Bootloader之uBoot简介
2012-03-13 13:44
274 查看
来自http://blog.ednchina.com/hhuwxf/1915416/message.aspx,感谢作者
一、Bootloader的引入
从前面的硬件实验可以知道,系统上电之后,需要一段程序来进行初始化:关闭
WATCHDOG、改变系统时钟、初始化存储控制器、将更多的代码复制到内存中等等。如果它能将操作系统内核(无论从本地,比如Flash;还是从远端,
比如通过网络)复制到内存中运行,就称这段程序为Bootloader。
简单地说,Bootloader就是这么一小段程序,它在系统上电时开始执行,初始化硬件设备、准备好软件环境,最后调用操作系统内核。
可以增强Bootloader的功能,比如增加网络功能、从PC上通过串口或网络下载文件、
烧写文件、将Flash上压缩的文件解压后再运行等──这就是一个功能更为强大的Bootloader,也称为Monitor。实际上,在最终产品中用户
并不需要这些功能,它们只是为了方便开发。
Bootloader的实现严重依赖于具体硬件,在嵌入式系统中硬件配置千差万别,即使是相
同的CPU,它的外设(比如Flash)也可能不同,所以不可能有一个Bootloader支持所有的CPU、所有的电路板。即使是支持CPU架构比较多
的U-Boot,也不是一拿来就可以使用的(除非里面的配置刚好与你的板子相同),需要进行一些移植。
二、Bootloader的启动方式
CPU上电后,会从某个地址开始执行。比如MIPS结构的CPU会从0xBFC00000取
第一条指令,而ARM结构的CPU则从地址0x0000000开始。嵌入式单板中,需要把存储器件ROM或Flash等映射到这个地
址,Bootloader就存放在这个地址开始处,这样一上电就可以执行。
在开发时,通常需要使用各种命令操作Bootloader,一般通过串口来连接PC和开发
板,可以在串口上输入各种命令、观察运行结果等。这也只是对开发人员才有意义,用户使用产品时是不用接串口来控制Bootloader的。从这个观点来
看,Bootloader可以分为两种操作模式(OperationMode):
(1)启动加载(Bootloading)模式。
上电后,Bootloader从板子上的某个固态存储设备上将操作系统加载到RAM中运行,整个过程并没有用户的介入。产品发布时,Bootloader工作在这种模式下。
(2)下载(Downloading)模式。
在这种模式下,开发人员可以使用各种命令,通过串口连接或网络连接等通信手段从主机(Host)下载文件(比如内核映像、文件系统映像),将它们直接放在内存运行或是烧入Flash类固态存储设备中。
板子与主机间传输文件时,可以使用串口的xmodem/ymodem/zmodem协议,它们使用简单,只是速度比较慢;还可以使用网络通过tftp、nfs协议来传输,这时,主机上要开启tftp、nfs服务;还有其他方法,比如USB等。
像Blob或U-Boot等这样功能强大的Bootloader通常同时支持这两种工作模
式,而且允许用户在这两种工作模式之间进行切换。比如,U-Boot在启动时处于正常的启动加载模式,但是它会延时若干秒(这可以设置)等待终端用户按下
任意键而将U-Boot切换到下载模式。如果在指定时间内没有用户按键,则U-Boot继续启动Linux内核。
编辑]15.1.2Bootloader的结构和启动过程
1.概述
在移植之前先了解Bootloader的一些通用概念,对理解它的代码会有所帮助。
在一个嵌入式Linux系统中,从软件的角度通常可以分为4个层次:
(1)引导加载程序,包括固化在固件(firmware)中的boot代码(可选)和Bootloader两大部分。
有些CPU在运行Bootloader之前先运行一段固化的程序(固件,firmware),比如x86结构的CPU就是先运行BIOS中的固件,然后才运行硬盘第一个分区(MBR)中的Bootloader。
在大多嵌入式系统中并没有固件,Bootloader是上电后执行的第一个程序。
(2)Linux内核。
特定于嵌入式板子的定制内核以及内核的启动参数。内核的启动参数可以是内核默认的,或是由Bootloader传递给它的。
(3)文件系统。
包括根文件系统和建立于Flash内存设备之上的文件系统。里面包含了Linux系统能够运行所必需的应用程序、库等,比如可以给用户提供操作Linux的控制界面的shell程序,动态连接的程序运行时需要的glibc或uClibc库,等等。
(4)用户应用程序。
特定于用户的应用程序,它们也存储在文件系统中。有时在用户应用程序和内核层之间可能还会包括一个嵌入式图形用户界面。常用的嵌入式GUI有:Qtopia和MiniGUI等。
显然,在嵌入系统的固态存储设备上有相应的分区来存储它们,图15.1是一个典型的分区结构。[[Image:]]
图15.1嵌入式Linux系统中的典型分区结构
“Boot
parameters”分区中存放一些可设置的参数,比如IP地址、串口波特率、要传递给内核的命令行参数等。正常启动过程中,Bootloader首先
运行,然后它将内核复制到内存中(也有些内核可以在固态存储设备上直接运行),并且在内存某个固定的地址设置好要传递给内核的参数,最后运行内核。内核启
动之后,它会挂接(mount)根文件系统(“Rootfilesystem”),启动文件系统中的应用程序。
2.Bootloader的两个阶段
Bootloader的启动过程启动过程可以分为单阶段(Single
Stage)、多阶段(Multi-Stage)两种。通常多阶段的Bootloader能提供更为复杂的功能,以及更好的可移植性。从固态存储设备上启
动的Bootloader大多都是2阶段的启动过程。这从前面的硬件实验可以很好地理解这点:第一阶段使用汇编来实现,它完成一些依赖于CPU
体系结构的初始化,并调用第二阶段的代码。第二阶段则通常使用C语言来实现,这样可以实现更复杂的功能,而且代码会有更好的可读性和可移植性。
一般而言,这两个阶段完成的功能可以如下分类,但这不是绝对的:
(1)Bootloader第一阶段的功能。
硬件设备初始化。
为加载Bootloader的第二阶段代码准备RAM空间。
拷贝Bootloader的第二阶段代码到RAM空间中。
设置好栈。
跳转到第二阶段代码的C入口点。
在第一阶段进行的硬件初始化一般包括:关闭WATCHDOG、关中断、设置CPU的速度和时钟频率、RAM初始化等。这些并不都是必需的,比如S3C2410/S3C2440的开发板所使用的U-Boot中,就将CPU的速度和时钟频率的设置放在第二阶段。
甚至,将第二阶段的代码复制到RAM空间中也不是必需的,对于NORFlash等存储设备,完全可以在上面直接执行代码,只不过这相比在RAM中执行效率大为降低。
(2)Bootloader第二阶段的功能。
初始化本阶段要使用到的硬件设备。
检测系统内存映射(memorymap)。
将内核映像和根文件系统映像从Flash上读到RAM空间中。
为内核设置启动参数。
调用内核。
为了方便开发,至少要初始化一个串口以便程序员与Bootloader进行交互。
所谓检测内存映射,就是确定板上使用了多少内存,它们的地址空间是什么。由于嵌入式开发中,Bootloader多是针对某类板子进行编写,所以可以根据板子的情况直接设置,不需要考虑可以适用于各类情况的复杂算法。
Flash上的内核映像有可能是经过压缩的,在读到RAM之后,还需要进行解压。当然,对于有自解压功能的内核,不需要Bootloader来解压。
将根文件系统映像复制到RAM中,这不是必需的。这取决于是什么类型的根文件系统,以及内核访问它的方法。
为内核设置启动参数将在下一小节介绍。
将内核存放在适当的位置后,直接跳到到它的入口点即可调用内核。调用内核之前,下列条件要满足:
(1)CPU寄存器的设置。
R0=0
R1=机器类型ID;对于ARM结构的CPU,其机器类型ID可以参见linux/arch/arm/tools/mach-types。
R2=启动参数标记列表在RAM中起始基地址
(2)CPU工作模式。
必须禁止中断(IRQs和FIQs)
CPU必须SVC模式
(3)Cache和MMU的设置。
MMU必须关闭
指令Cache可以打开也可以关闭
数据Cache必须关闭
如果用C语言,可以像下列示例代码一样来调用内核:
void(*theKernel)(intzero,intarch,u32
params_addr)=(void(*)(int,int,u32))KERNEL_RAM_BASE;……
theKernel(0,ARCH_NUMBER,(u32)kernel_params_start);
3.Bootloader与内核的交互
Bootloader与内核的交互是单向的,Bootloader将各类参数传给内核。由于它们不能同时运行,传递办法只有一个:Bootloader将参数放在某个约定的地方之后,再启动内核,内核启动后从这个地方获得参数。
除了约定好参数存放的地址外,还要规定参数的结构。Linux2.4.x
以后的内核都期望以标记列表(tagged
list)的形式来传递启动参数。标记,就是一种数据结构;标记列表,就是挨着存放的多个标记。标记列表以标记ATAG_CORE
开始,以标记ATAG_NONE
结束。标记的数据结构为tag,它由一个tag_header结构和一个联合(union)组成。tag_header结构表示标记的类型及长度,比如是
表示内存还是表示命令行参数等。对于不同类型的标记使用不同的联合(union),比如表示内存时使用tag_mem32,表示命令行时使用
tag_cmdline。数据结构tag和tag_header定义在Linux内核源码的include/asm/setup.h头文件中:
viewsource
print?
下面以设置内存标记、命令行标记为例说明参数的传递:
(1)设置标记ATAG_CORE。
标记列表以标记ATAG_CORE开始,假设Bootloader与内核约定的参数存放地址为0x30000100,则可以以如下代码设置标记ATAG_CORE:
params=(structtag*)0x30000100;
<br>params->hdr.tag=ATAG_CORE;params->hdr.size=
tag_size(tag_core);<br>params->u.core.flags=0;
params->u.core.pagesize=0;params->u.core.rootdev=0;
<br>params=tag_next(params);
其中,tag_next定义如下,它指向当前标记的末尾:
#definetag_next(t)((structtag*)((u32*)(t)+(t)->hdr.size))
(2)设置内存标记。
假设开发板使用的内存起始地址为0x30000000,大小为0x4000000,则内存标记可以如下设置:
params->hdr.tag=ATAG_MEM;
params->hdr.size=tag_size(tag_mem32);
params->u.mem.start=0x30000000;
params->u.mem.size=0x4000000;
params=tag_next(params);
(3)设置命令行标记。
命令行就是一个字符串,它被用来控制内核的一些行为。比如"root=/dev
/mtdblock2init=/linuxrc
console=ttySAC0"表示根文件系统在MTD2分区上,系统启动后执行的第一个程序为/linuxrc,控制台为ttySAC0(即第一个串
口)。
命令行可以在Bootloader中通过命令设置好,然后如下构造标记传给内核:
char*p="root=/dev/mtdblock2init=/linuxrcconsole=ttySAC0";
params->hdr.tag=ATAG_CMDLINE;
params->hdr.size=(sizeof(structtag_header)+strlen(p)+1+4)>>2;
strcpy(params->u.cmdline.cmdline,p);
params=tag_next(params);
(4)设置标记ATAG_NONE。
标记列表以标记ATAG_NONE结束,如下设置:
params->hdr.tag=ATAG_NONE;
params->hdr.size=0;
常用Bootloader介绍
现在Bootloader种类繁多,比如x86上有LILO、GRUB等。对于ARM架构的CPU,有U-Boot、Vivi等。它们各有特点,下面列出Linux的开放源代码的Bootloader及其支持的体系架构,如表15.1所示。
开放源码的Linux引导程序
<tablefck__showtableborders?="">
Bootloader
Monitor
描述
X86
ARM
PowerPC
LILO
否
Linux磁盘引导程序
是
否
否
GRUB
否
GNU的LILO替代程序
是
否
否
Loadlin
否
从DOS引导Linux
是
否
否
ROLO
否
从ROM引导Linux而不需要BIOS
是
否
否
Etherboot
否
通过以太网卡启动Linux系统的固件
是
否
否
LinuxBIOS
否
完全替代BUIS的Linux引导程序
是
否
否
BLOB
是
LART等硬件平台的引导程序
否
是
否
U-Boot
是
通用引导程序
是
是
是
RedBoot
是
基于eCos的引导程序
是
是
是
Vivi
是
Mizi公司针对SAMSUNG的ARMCPU设计的引导程序
否
是
否
对于本书使用的S3C2410/S3C2440开发板,U-Boot和Vivi是两个好选
择。Vivi是Mizi公司针对SAMSUNG的ARM架构CPU专门设计的,基本上可以直接使用,命令简单方便。不过其初始版本只支持串口下载,速度较
慢。在网上出现了各种改进版本:支持网络功能、USB功能、烧写YAFFS文件系统映像等。U-Boot则支持大多CPU,可以烧写EXT2、JFFS2
文件系统映像,支持串口下载、网络下载,并提供了大量的命令。相对于Vivi,它的使用更复杂,但是可以用来更方便地调试程序。
2U-Boot分析与移植
2.1U-Boot工程简介
U-Boot,全称为UniversalBoot
Loader,即通用Bootloader,是遵循GPL条款的开放源代码项目。其前身是由德国DENX软件工程中心的Wolfgang
Denk基于8xxROM的源码创建的PPCBOOT工程。后来整理代码结构使得非常容易增加其他类型的开发板、其他架构的CPU(原来只支持
PowerPC);增加更多的功能,比如启动Linux、下载S-Record格式的文件、通过网络启动、通过PCMCIA/CompactFLash
/ATAdisk/SCSI等方式启动。增加ARM架构CPU及其他更多CPU的支持后,改名为U-Boot。
它的名字“通用”有两层含义:可以引导多种操作系统、支持多种架构的CPU。它支持如下操作
系统:Linux、NetBSD、
VxWorks、QNX、RTEMS、ARTOS、LynxOS等,支持如下架构的CPU:PowerPC、MIPS、x86、ARM、NIOS、
XScale等。
U-Boot有如下特性:
开放源码;
支持多种嵌入式操作系统内核,如Linux、NetBSD、VxWorks、QNX、RTEMS、ARTOS、LynxOS;
支持多个处理器系列,如PowerPC、ARM、x86、MIPS、XScale;
较高的可靠性和稳定性;
高度灵活的功能设置,适合U-Boot调试、操作系统不同引导要求、产品发布等;
丰富的设备驱动源码,如串口、以太网、SDRAM、FLASH、LCD、NVRAM、EEPROM、RTC、键盘等;
较为丰富的开发调试文档与强大的网络技术支持;
支持NFS挂载、RAMDISK(压缩或非压缩)形式的根文件系统
支持NFS挂载、从FLASH中引导压缩或非压缩系统内核;
可灵活设置、传递多个关键参数给操作系统,适合系统在不同开发阶段的调试要求与产品发布,尤对Linux支持最为强劲;
支持目标板环境变量多种存储方式,如FLASH、NVRAM、EEPROM;
CRC32校验,可校验FLASH中内核、RAMDISK镜像文件是否完好;
上电自检功能:SDRAM、FLASH大小自动检测;SDRAM故障检测;CPU型号;
特殊功能:XIP内核引导;
可以从http://sourceforge.net/projects/u-boot获得U-Boot的最新版本,如果使用过程中碰到问题或是发现Bug,可以通过邮件列表网站http://lists.sourceforge.net/lists/listinfo/u-boot-users/获得帮助。
最新的更新代码地址http://www.denx.de/wiki/U-Boot/WebHome
2.2U-Boot源码结构
本书在u-boot-1.1.6的基础上进行分析和移植,从sourceforge网站下载u-boot-1.1.6.tar.bz2后解压即得到全部源码。U-Boot源码目录结构比较简单、独立,目录结构也比较浅,很容易全部掌握。
u-boot-1.1.6根目录下共有26个子目录,可以分为4类:
(1)平台相关的或开发板相关的。
(2)通用的函数。
(3)通用的设备驱动程序。
(4)U-Boot工具、示例程序、文档。
先将这26个目录的功能与作用如表15.2所示。
表2U-Boot顶层目录说明
<tablefck__showtableborders?="">
目录
特性
解释说明
board
开发板相关
对应不同配置的电路板(即使CPU相同),比如smdk2410、sbc2410x
cpu
平台相关
对应不同的CPU,比如arm920t、arm925t、i386等;在它们的子目录下仍可以进一步细分,比如arm920t下就有at91rm9200、s3c24x0
lib_i386类似
某一架构下通用的文件
include
通用的函数
头文件和开发板配置文件,开发板的配置文件都放在include/configs目录下,U-Boot没有makemenuconfig类似的莱单来进行可视化配置,需要手动地修改配置文件中的宏定义
lib_generic
通用的库函数,比如printf等
common
通用的函数,多是对下一层驱动程序的进一步封装
disk
通用的设备驱动程序
硬盘接口程序
drivers
各类具体设备的驱动程序,基本上可以通用,它们通过宏从外面引入平台/开发板相关的函数
dtt
数字温度测量器或者传感器的驱动
fs
文件系统
nand_spl
U-Boot一般从ROM、NORFlash等设备启动,现在开始支持从NANDFlash启动,但是支持的CPU种类还不多
net
各种网络协议
post
上电自检程序
rtc
实时时钟的驱动
doc
文档
开发、使用文档
examples
示例程序
一些测试程序,可以使用U-Boot下载后运行
tools
工具
制作S-Record、U-Boot格式映像的工具,比如mkimage
U-Boot中各目录间也是有层次结构的,虽然这种分法不是绝对的,但是在移植过程中可以提供一些指导意义,如图2所示。
2U-Boot顶层目录的层次结构
比如common/cmd_nand.c文件提供了操作NAND
Flash的各种命令,这些命令通过调用drivers/nand/nand_base.c中的擦除、读写函数来实现。这些函数针对NAND
Flash的共性作了一些封装,将平台/开发板相关的代码用宏或外部函数来代替。而这些宏与外部函数,如果与平台相关,就要在下一层次的cpu
/xxx(xxx表示某型号的CPU)中实现;如果与开发板相关,就要在下一层次的board/xxx目录(xxx表示某款开发板)中实现。本书移植的
U-Boot,就是在cpu/arm920t/s3c24x0目录下增加了一个nand_flash.c文件来实现这些函数。
以增加烧写yaffs文件系统映像的功能为例──就是在common目录下的
cmd_nand.c中增加命令,比如nand
write.yaffs:这个命令要调用drivers/nand/nand_util.c中的相应函数,针对yaffs文件系统的特点依次调用擦除、烧
写函数。而这些函数依赖于drivers/nand/nand_base.c、cpu/arm920t/s3c24x0/nand_flash.c文件中
的相关函数。
目前u-boot-1.1.6支持10种架构──根目录下有10个类似lib_i386的目
录、31个型号(类型)的CPU──cpu目录下有31个子目录,214种开发板──board目录下有214个子目录,很容易从中找到与自己的板子相似
的配置,在上面稍作修改即可使用。
2.3U-Boot的配置、编译、连接过程
1.U-Boot初体验
u-boot-1.1.6中有几千个文件,要想了解对于某款开发板,使用哪些文件、哪个文件首先执行、可执行文件占用内存的情况,最好的方法就是阅读它的Makefile。
根据顶层Readme文件的说明,可以知道如果要使用开发板board/<board_name>,就先执行“make<board_name>_config”命令进行配置,然后执行“makeall”,就可以生成如下3个文件:
u-boot.bin:二进制可执行文件,它就是可以直接烧入ROM、NORFlash的文件。
u-boot:ELF格式的可执行文件
u-boot.srec:MotorolaS-Record格式的可执行文件
对于S3C2410的开发板,执行“makesmdk2410_config”、“makeall”后生成的u-boot.bin可以烧入NORFlash中运行。启动后可以看到串口输出一些信息后进入控制界面,等待用户的输入。
对于S3C2440的开发板,烧入上面生成的u-boot.bin,串口无输出,需要修改代码。
在修改代码之前,先看看上面两个命令“makesmdk2410_config”、“makeall”做了什么事情,以了解程序的流程,知道要修改哪些文件。
另外,编译U-Boot成功后,还会在它的tools子目录下生成一些工具,比如mkimage等。将它们复制到/usr/local/bin目录下,以后就可以直接使用它们了,比如编译内核时,会使用mkimage来生成U-Boot格式的内核映像文件uImage。
2.U-Boot的配置过程
在顶层Makefile中可以看到如下代码:
SRCTREE:=$(CURDIR)
……
MKCONFIG:=$(SRCTREE)/mkconfig
……
smdk2410_config:unconfig
@$(MKCONFIG)$(@:_config=)armarm920tsmdk2410NULLs3c24x0
假定我们在u-boot-1.1.6的根目录下编译,则其中的MKCONFIG就是根目录下
的mkconfig文件。$(@:_config=)的结果就是将“smdk2410_config”中的“_config”去掉,结果为
“smdk2410”。所以“makesmdk2410_config”实际上就是执行如下命令:
./mkconfigsmdk2410armarm920tsmdk2410NULLs3c24x0
再来看看mkconfig的作用,在mkconfig文件开头第6行给出了它的用法:
06#Parameters:TargetArchitectureCPUBoard[VENDOR][SOC]
这里解释一下概念,对于S3C2410、S3C2440,它们被称为SoC(System
onChip),上面除CPU外,还集成了包括UART、USB控制器、NAND
Flash控制器等等设备(称为片内外设)。S3C2410/S3C2440中的CPU为arm920t。
以下,分步骤分析mkconfig的作用:
(1)确定开发板名称BOARD_NAME。
viewsource
print?
对于“./mkconfigsmdk2410armarm920tsmdk2410NULLs3c24x0”命令,其中没有“--”、“-a”、“-n”等符号,所以第14~22行没做任何事情。第11、12行两个变量仍维持原来的值。
执行完第23行后,BOARD_NAME的值等于第1个参数,即“smdk2410”。
(2)创建到平台/开发板相关的头文件的链接。
略过mkconfig文件中的一些没有起作用的行:
viewsource
print?
第33行判断源代码目录和目标文件目录是否一样,可以选择在其他目录下编译U-Boot,这可以令源代码目录保持干净,可以同时使用不同的配置进行编译。不过本书直接在源代码目录下编译的,第33行的条件不满足,将执行else分支的代码。
第46~48行进入include目录,删除asm文件(这是上一次配置时建立的链接文件),然后再次建立asm文件,并令它链接向asm-$2目录,即asm-arm。
继续往下看代码:
viewsource
print?
第51行删除asm-$2/arch目录,即asm-arm/arch。
对于“./mkconfigsmdk2410armarm920tsmdk2410NULLs3c24x0”命令,$6为“s3c24x0”,不为空,也不是“NULL”,所以第53行的条件不满足,将执行else分支。
第56行中,LNPREFIX为空,所以这个命令实际上就是:ln-sarch-$6asm-$2/arch,即:ln-sarch-s3c24x0asm-arm/arch。
第60、61行重新建立asm-arm/proc文件,并让它链接向proc-armv目录。
(3)创建顶层Makefile包含的文件include/config.mk。
对于“./mkconfigsmdk2410armarm920tsmdk2410NULLs3c24x0”命令,上面几行代码创建的config.mk文件内容如下:
viewsource
print?
ARCH=arm
CPU=arm920t
BOARD=smdk2410
SOC=s3c24x0
(4)创建开发板相关的头文件include/config.h。
viewsource
print?
前面说过,APPEND维持原值“no”,所以config.h被重新建立,它的内容如下:
/*Automaticallygenerated-donotedit*/
#include<configs/smdk2410.h>"
现在总结一下,配置命令“make
smdk2410_config”,实际的作用就是执行“./mkconfigsmdk2410armarm920tsmdk2410
NULLs3c24x0”命令。假设执行“./mkconfig$1$2$3$4$5$6”命令,则将产生如下结果:
(1)开发板名称BOARD_NAME等于$1;
(2)创建到平台/开发板相关的头文件的链接:
ln-sasm-$2asm
ln-sarch-$6asm-$2/arch
ln-sproc-armvasm-$2/proc#如果$2不是arm的话,此行没有
(3)创建顶层Makefile包含的文件include/config.mk。
ARCH=$2
CPU=$3
BOARD=$4
VENDOR=$5#$5为空,或者是NULL的话,此行没有
SOC=$6#$6为空,或者是NULL的话,此行没有
(4)创建开发板相关的头文件include/config.h。
/*Automaticallygenerated-donotedit*/
#include<configs/$1.h>"
从这4个结果可以知道,如果要在board目录下新建一个开发
板<board_name>的目录,则在include/config目录下也要建立一个文件<board_name>.h,里
面存放的就是开发板<board_name>的配置信息。
U-Boot还没有类似Linux一样的可视化配置界面(比如使用makemenuconfig来配置),要手动修改配置文件include/config/<board_name>.h来裁减、设置U-Boot。
配置文件中有两类宏:
(1)一类是选项(Options),前缀为“CONFIG_”,它们用于选择CPU、SOC、开发板类型,设置系统时钟、选择设备驱动等。比如:
#defineCONFIG_ARM920T1/*ThisisanARM920TCore*/
#defineCONFIG_S3C24101/*inaSAMSUNGS3C2410SoC*/
#defineCONFIG_SMDK24101/*onaSAMSUNGSMDK2410Board*/
#defineCONFIG_SYS_CLK_FREQ12000000/*theSMDK2410has12MHzinputclock*/
#defineCONFIG_DRIVER_CS89001/*wehaveaCS8900on-board*/
(2)另一类是参数(Setting),前缀为“CFG_”,它们用于设置malloc缓冲池的大小、U-Boot的提示符、U-Boot下载文件时的默认加载地址、Flash的起始地址等。比如:
#defineCFG_MALLOC_LEN(CFG_ENV_SIZE+128*1024)
#defineCFG_PROMPT"100ASK>"/*MonitorCommandPrompt*/
#defineCFG_LOAD_ADDR0x33000000/*defaultloadaddress*/
#definePHYS_FLASH_10x00000000/*FlashBank#1*/
从下面的编译、连接过程可知,U-Boot中几乎每个文件都被编译和连接,但是这些文件是否包含有效的代码,则由宏开关来设置。比如对于网卡驱动drivers/cs8900.c,它的格式为:
#include<common.h>/*将包含配置文件include/config/<board_name>.h*/
……
#ifdefCONFIG_DRIVER_CS8900
/*实际的代码*/
……
#endif/*CONFIG_DRIVER_CS8900*/
如果定义了宏CONFIG_DRIVER_CS8900,则文件中包含有效的代码;否则,文件被注释为空。
可以这样粗糙地认为,“CONFIG_”除了设置一些参数外,主要用来设置U-Boot的功能、选择使用文件中的哪一部分;而“CFG_”用来设置更细节的参数。
3.U-Boot的编译、连接过程
配置完后,执行“makeall”即可编译,从Makefile中可以了解U-Boot使用了哪些文件、哪个文件首先执行、可执行文件占用内存的情况。
先确定用到哪些文件,下面只摘取Makefile中与arm相关的部分:
viewsource
print?
第117、164行用于包含其他的config.mk文件,第117行所要包含文件的就是在
上面的配置过程中制作出来的include/config.mk文件,其中定义了ARCH、CPU、BOARD、SOC等4个变量的值为arm、
arm920t、smdk2410、s3c24x0。
第164行包含顶层目录的config.mk文件,它根据上面4个变量的值确定了编译器、编译选项等。其中对我们理解编译过程有帮助的是BOARDDIR、LDFLAGS的值,config.mk中:
viewsource
print?
在board/smdk2410/config.mk中,定义了“TEXT_BASE=
0x33F80000”。所以,最终结果如下:BOARDDIR为smdk2410;LDFLAGS中有“-T
board/smdk2410/u-boot.lds-Ttext0x33F80000”字样。
继续往下看Makefile:
166#########################################################################
167#U-Bootobjects....orderisimportant(i.e.startmustbefirst)
168
169OBJS=cpu/$(CPU)/start.o
……
193LIBS=lib_generic/libgeneric.a
194LIBS+=board/$(BOARDDIR)/lib$(BOARD).a
195LIBS+=cpu/$(CPU)/lib$(CPU).a
……
199LIBS+=lib_$(ARCH)/lib$(ARCH).a
200LIBS+=fs/cramfs/libcramfs.afs/fat/libfat.afs/fdos/libfdos.afs/jffs2/libjffs2.a\
201fs/reiserfs/libreiserfs.afs/ext2/libext2fs.a
202LIBS+=net/libnet.a
……
212LIBS+=$(BOARDLIBS)
213
……
从第169行得知,OBJS的第一个值为“cpu/$(CPU)/start.o”,即“cpu/arm920t/start.o”。
第193~213行指定了LIBS变量就是平台/开发板相关的各个目录、通用目录下相应的
库,比如:lib_generic/libgeneric.a、board/smdk2410/libsmdk2410.a、cpu/arm920t
/libarm920t.a、lib_arm/libarm.a、fs/cramfs/libcramfs.afs/fat/libfat.a等。
OBJS、LIBS所代表的.o、.a文件就是U-Boot的构成,它们通过如下命令由相应的源文件(或相应子目录下的文件)编译得到。
268$(OBJS):
269$(MAKE)-Ccpu/$(CPU)$(if$(REMOTE_BUILD),$@,$(notdir$@))
270
271$(LIBS):
272$(MAKE)-C$(dir$(subst$(obj),,$@))
273
274$(SUBDIRS):
275$(MAKE)-C$@all
276
第268、269两行的规则表示,对于OBJS中的每个成员,都将进入cpu/$(CPU)目录(即cpu/arm920t)编译它们。现在OBJS为cpu/arm920t/start.o,它将由cpu/arm920t/start.S编译得到。
第271、272两行的规则表示,对于LIBS中的每个成员,都将进入相应的子目录执行“make”命令。这些子目录中的Makefile,结构相似,它们将Makefle中指定的文件编译、连接成一个库文件。
当所有的OBJS、LIBS所表示的.o和.a文件都生成后,就剩最后的连接了,这对应Makefile中如下几行:
246$(obj)u-boot.srec:$(obj)u-boot
247$(OBJCOPY)${OBJCFLAGS}-Osrec$<$@
248
249$(obj)u-boot.bin:$(obj)u-boot
250$(OBJCOPY)${OBJCFLAGS}-Obinary$<$@
251
……
262$(obj)u-boot:dependversion$(SUBDIRS)$(OBJS)$(LIBS)$(LDSCRIPT)
263UNDEF_SYM=`$(OBJDUMP)-x$(LIBS)|sed-n-e's/.*\(__u_boot_cmd_.*\)/-u\1/p'|sort|uniq`;\
264cd$(LNDIR)&&$(LD)$(LDFLAGS)$$UNDEF_SYM$(__OBJS)\
265--start-group$(__LIBS)--end-group$(PLATFORM_LIBS)\
266-Mapu-boot.map-ou-boot
267
先使用第262~266的规则连接得到ELF格式的u-boot,最后转换为二进制格式u-
boot.bin、S-Record格式u-boot.srec。LDFLAGS确定了连接方式,其中的“-T
board/smdk2410/u-boot.lds-Ttext
0x33F80000”字样指定了程序的布局、地址。board/smdk2410/u-boot.lds文件如下:
28SECTIONS
29{
30.=0x00000000;
31
32.=ALIGN(4);
33.text:
34{
35cpu/arm920t/start.o(.text)
36*(.text)
37}
38
39.=ALIGN(4);
40.rodata:{*(.rodata)}
41
42.=ALIGN(4);
43.data:{*(.data)}
44
45.=ALIGN(4);
46.got:{*(.got)}
47
48.=.;
49__u_boot_cmd_start=.;
50.u_boot_cmd:{*(.u_boot_cmd)}
51__u_boot_cmd_end=.;
52
53.=ALIGN(4);
54__bss_start=.;
55.bss:{*(.bss)}
56_end=.;
57}
从第35行可知,cpu/arm920t/start.o被放在程序的最前面,所以U-Boot的入口点在cpu/arm920t/start.S中。
现在来总结一下U-Boot的编译流程:
(1)首先编译cpu/$(CPU)/start.S,对于不同的CPU,还可能编译cpu/$(CPU)下的其他文件。
(2)然后,对于平台/开发板相关的每个目录、每个通用目录,都使用它们各自的Makefile生成相应的库。
(3)将1、2步骤生成的.o、.a文件按照board/$(BOARDDIR)/config.mk文件中指定的代码段起始地址、board/$(BOARDDIR)/u-boot.lds连接脚本进行连接。
(4)第3步得到的是ELF格式的U-Boot,后面Makefile还会将它转换为二进制格式、S-Record格式。
2.4U-Boot的启动过程源码分析
首先强调,本书使用的U-Boot从NORFlash启动,下面以开发板smdk2410的U-Boot为例。
U-Boot属于两阶段的Bootloader,第一阶段的文件为cpu/arm920t/start.S和board/smdk2410/lowlevel_init.S,前者是平台相关,后者是开发板相关。
U-Boot第一阶段代码分析
它与1.2节中描述的Bootloader第一阶段所完成的功能可以一一对应:
(1)硬件设备初始化。
依次完成如下设置:将CPU的工作模式设为管理模式(svc),关闭WATCHDOG,设置FCLK、HCLK、PCLK的比例(即设置CLKDIVN寄存器),关闭MMU、CACHE。
代码都在cpu/arm920t/start.S中,注释也比较完善,读者有不明白的地方可以参考前面硬件实验的相关章节。
(2)为加载Bootloader的第二阶段代码准备RAM空间。
所谓准备RAM空间,就是初始化内存芯片,使它可用。对于S3C2410/S3C2440,
通过在start.S中调用lowlevel_init函数来设置存储控制器,使得外接的SDRAM可用。代码在board/smdk2410
/lowlevel_init.S中。
注意:lowlevel_init.S文件是开发板相关的,这表示如果外接的设备不一样,可以修改lowlevel_init.S文件中的相关宏。
lowlevel_init函数并不复杂,只是要注意这时的代码、数据都只保存在NORFlash上,内存中还没有,所以读取数据时要变换地址。代码如下:
129_TEXT_BASE:
130.wordTEXT_BASE
131
132.globllowlevel_init
133lowlevel_init:
134/*memorycontrolconfiguration*/
135/*maker0relativethecurrentlocationsothatit*/
136/*readsSMRDATAoutofFLASHratherthanmemory!*/
137ldrr0,=SMRDATA
138ldrr1,_TEXT_BASE
139subr0,r0,r1
140ldrr1,=BWSCON/*BusWidthStatusController*/
141addr2,r0,#13*4
1420:
143ldrr3,[r0],#4
144strr3,[r1],#4
145cmpr2,r0
146bne0b
147
148/*everythingisfinenow*/
149movpc,lr
150
151.ltorg
152/*theliteralpoolsorigin*/
153
154SMRDATA:/*13个寄存器的值*/
155.word……
156.word……
第137~139行进行地址变换,因为这时候内存中还没有数据,不能使用连接程序时确定的地址来读取数据:
第137行中SMRDATA表示这13个寄存器的值存放的开始地址(连接地址),值为0x33F8xxxx,处于内存中。
第138行获得代码段的起始地址,它就是第130行中的“TEXT_BASE”,其值在board/smdk2410/config.mk中定义:“TEXT_BASE=0x33F80000”。
第139行将0x33F8xxxx与0x33F80000相减,这就是13个寄存器值在NORFlash上存放的开始地址。
(3)拷贝Bootloader的第二阶段代码到RAM空间中。
这里将整个U-Boot的代码(包括第一、第二阶段)都复制到SDRAM中,这在cpu/arm920t/start.S中实现:
164relocate:/*将U-Boot复制到RAM中*/
165adrr0,_start/*r0=当前代码的开始地址*/
166ldrr1,_TEXT_BASE/*r1=代码段的连接地址*/
167cmpr0,r1/*测试现在是在Flash中还是在RAM中*/
168beqstack_setup/*如果已经在RAM中(这通常是调试时,直接下载到RAM中),
*则不需要复制
*/
169
170ldrr2,_armboot_start/*_armboot_start在前面定义,是第一条指令的运行地址*/
171ldrr3,_bss_start/*在连接脚本u-boot.lds中定义,是代码段的结束地址*/
172subr2,r3,r2/*r2=代码段长度*/
173addr2,r0,r2/*r2=NORFlash上代码段的结束地址*/
174
175copy_loop:
176ldmiar0!,{r3-r10}/*从地址[r0]处获得数据*/
177stmiar1!,{r3-r10}/*复制到地址[r1]处*/
178cmpr0,r2/*判断是否复制完毕*/
179blecopy_loop/*没复制完,则继续*/
(4)设置好栈。
栈的设置灵活性很大,只要让sp寄存器指向一段没有使用的内存即可。
182/*Setupthestack*/
183stack_setup:
184ldrr0,_TEXT_BASE/*_TEXT_BASE为代码段的开始地址,值为0x33F80000*/
185subr0,r0,#CFG_MALLOC_LEN/*代码段下面,留出一段内存以实现malloc*/
186subr0,r0,#CFG_GBL_DATA_SIZE/*再留出一段内存,存一些全局参数*/
187#ifdefCONFIG_USE_IRQ
188subr0,r0,#(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)/*IRQ、FIQ模式的栈*/
189#endif
190subsp,r0,#12/*最后,留出12字节的内存给abort异常,
*往下的内存就都是栈了
*/
191
到了这一步,读者可以知道内存的使用情况了,如下图所示(图中与上面的划分稍有不同,这是因为在cpu/arm920t/cpu.c中的cpu_init函数中才真正为IRQ、FIQ模式划分了栈):[[Image:]]
图3U-Boot内存使用情况
(5)跳转到第二阶段代码的C入口点。
在跳转之前,还要清除BSS段(初始值为0、无初始值的全局变量、静态变量放在BSS段),代码如下:
192clear_bss:
193ldrr0,_bss_start/*BSS段的开始地址,它的值在连接脚本u-boot.lds中确定*/
194ldrr1,_bss_end/*BSS段的结束地址,它的值在连接脚本u-boot.lds中确定*/
195movr2,#0x00000000
196
197clbss_l:strr2,[r0]/*往BSS段中写入0值*/
198addr0,r0,#4
199cmpr0,r1
200bleclbss_l
201
现在,C函数的运行环境已经完全准备好,通过如下命令直接跳转(这之后,程序才在内存中执行),它将调用lib_arm/board.c中的start_armboot函数,这是第二阶段的入口点:
223ldrpc,_start_armboot
224
225_start_armboot:.wordstart_armboot
226
U-Boot第二阶段代码分析
它与15.1.2节中描述的Bootloader第二阶段所完成的功能基本上一致,不过顺序有点小差别。另外,U-Boot在启动内核之前可以让用户决定是否进入下载模式,即进入U-Boot的控制界面。
第二阶段从lib_arm/board.c中的start_armboot函数开始,先看从这个函数开始的程序流程图。
图3U-Boot第二阶段流程图
移植U-Boot的主要工作在于对硬件的初始化、驱动,所以下面讲解时将重点放在硬件的操作上。
(1)初始化本阶段要使用到的硬件设备。:最主要的是设置系统时钟、初始化串口,只要这两个设置好了,就可以从串口看到打印信息。
board_init函数设置MPLL、改变系统时钟,它是开发板相关的函数,在board/smdk2410/smdk2410.c中实现。值得注意的是,board_init函数中还保存了机器类型ID,这将在调用内核时传给内核,代码如下:
/*archnumberofSMDK2410-Board*/
gd->bd->bi_arch_number=MACH_TYPE_SMDK2410;/*值为193*/
串口的初始化函数主要是serial_init,它设置UART控制器,是CPU相关的函数,在cpu/arm920t/s3c24x0/serial.c中实现。
(2)检测系统内存映射(memory
map)。对于特定的开发板,其内存的分布是明确的,所以可以直接设置。board/smdk2410/smdk2410.c中的dram_init函数
指定了本开发板的内存起始地址为0x30000000,大小为0x4000000。代码如下:
intdram_init(void)
{
gd->bd->bi_dram[0].start=PHYS_SDRAM_1;/*即0x300000000*/
gd->bd->bi_dram[0].size=PHYS_SDRAM_1_SIZE;/*即0x4000000*/
return0;
}
这些设置的参数,将在后面向内核传递参数时用到。
(3)U-Boot命令的格式。
从图3可以知道,即使是内核的启动,也是通过U-Boot命令来实现的。U-Boot中每个命令都通过U_BOOT_CMD宏来定义,格式如下:
U_BOOT_CMD(name,maxargs,repeatable,command,"usage","help")
各项参数的意义为:
①name:命令的名字,注意,它不是一个字符串(不要用双引号括起来)。
②maxargs:最大的参数个数
③repeatable:命令是否可重复,可重复是指运行一个命令后,下次敲回车即可再次运行。
④command:对应的函数指针,类型为(*cmd)(structcmd_tbl_s*,int,int,char*[])。
⑤usage:简短的使用说明,这是个字符串。
⑥help:较详细的使用说明,这是个字符串。
宏U_BOOT_CMD在include/command.h中定义:
#defineU_BOOT_CMD(name,maxargs,rep,cmd,usage,help)\
cmd_tbl_t__u_boot_cmd_##nameStruct_Section={#name,maxargs,rep,cmd,usage,help}
Struct_Section也是在include/command.h中定义:
#defineStruct_Section__attribute__((unused,section(".u_boot_cmd")))
比如对于bootm命令,它如此定义:
U_BOOT_CMD(
bootm,CFG_MAXARGS,1,do_bootm,
“string1”,
“string2”
);
宏U_BOOT_CMD扩展开后就是:
cmd_tbl_t__u_boot_cmd_bootm__attribute__
((unused,section(".u_boot_cmd")))={“bootm”,CFG_MAXARGS,1,
do_bootm,“string1”,“string2”};
对于每个使用U_BOOT_CMD宏来定义的命令,其实都是在".u_boot_cmd"段中定义一个cmd_tbl_t结构。连接脚本u-boot.lds中有这么一段:
__u_boot_cmd_start=.;
.u_boot_cmd:{*(.u_boot_cmd)}
__u_boot_cmd_end=.;
程序中就是根据命令的名字在内存段__u_boot_cmd_start~__u_boot_cmd_end找到它的cmd_tbl_t结构,然后调用它的函数(请参考common/command.c中的find_cmd函数)。
内核的复制和启动,可以通过如下命令来完成:bootm从内存、ROM、NOR
Flash中启动内核,bootp则通过网络来启动,而nboot从NAND
Flash启动内核。它们都是先将内核映像从各种媒介中读出,存放在指定的位置;然后设置标记列表以给内核传递参数;最后跳到内核的入口点去执行。具体实
现的细节不再描述,有兴趣的读者可以阅读common/cmd_boot.c、common/cmd_net.c、common/cmd_nand.c来
了解它们的实现。
(4)为内核设置启动参数。
与15.1.2小节中《Bootloader与内核的交互》所描述的一样,U-Boot也是
通过标记列表向内核传递参数。并且,15.1.2小节中内存标记、命令行标记的示例代码就是取自U-Boot中的setup_memory_tags、
setup_commandline_tag函数,它们都是在lib_arm/armlinux.c中定义。一般而言,设置这两个标记就可以了,在配置文
件include/configs/smdk2410.h中增加如下两个配置项即可:
#defineCONFIG_SETUP_MEMORY_TAGS1
#defineCONFIG_CMDLINE_TAG1
对于ARM架构的CPU,都是通过lib_arm/armlinux.c中的
do_bootm_linux函数来启动内核。这个函数中,设置标记列表,最后通过“theKernel(0,
bd->bi_arch_number,
bd->bi_boot_params)”调用内核。其中,theKernel指向内核存放的地址(对于ARM架构的CPU,通常是
0x30008000),bd->bi_arch_number就是前面board_init函数设置的机器类型ID,而
bd->bi_boot_params就是标记列表的开始地址。
2.5U-Boot的移植
开发板smdk2410的配置适用于大多数S3C2410单板,或是只需要极少的修改即可使用。但是目前U-Boot中没有对S3C2440的支持,需要我们自己移植。
本书基于的S3C2410、S3C2440两款开发板,它们的外接硬件相同:
BANK0外接容量为1MB,位宽为8的NORFlash芯片AM29LV800
BANK3外接10M网卡芯片CS8900,位宽为16
BANK6外接两片容量为32MB、位宽为16的SDRAM芯片K4S561632,组成容量为64MB、位宽为32的内存
通过NANDFlash控制器外接容量为64MB,位宽为8的NANDFlash芯片K9S1208
对于NORFlash和NAND
Flash,如图15.4所示划分它们的使用区域。由于NAND
Flash的“位反转”现象比较常见,为保证数据的正确,在读写数据时需要使用ECC较验。另外,NAND
Flash在使用过程中、运输过程中还有可能出现坏块。所以本书选择在NORFlash中保存U-Boot,在NAND
Flash中保存内核和文件系统,并在使用U-Boot烧写内核、文件系统时,进行坏块检查、ECC较验。这样,即使NAND
Flash出现坏块导致内核或文件系统不能使用,也可以通过NORFlash中的U-Boot来重新烧写。[[Image:]]
图15.4开发板固态存储器分区划分
smdk2410开发板已经支持NOR
Flash芯片AM29LV800,U-Boot本身也已经支持jffs2文件系统映像的烧写。下面一步一步移植U-Boot(所有的修改都在补丁文件
u-boot-1.1.6_100ask24x0.patch里,读者可以直接打补丁),增加如下新功能:
同时支持本书使用的S3C2410和S3C2440开发板
支持串口xmodem协议
支持网卡芯片CS8900
支持NANDFlash读写
支持烧写yaffs文件系统映像
1.同时支持S3C2410和S3C2440
我们将在开发板smdk2410的基础上进行移植。
(1)新建一个开发板的相应目录和文件。
为了不破坏原来的代码,在board目录下将smdk2410复制为100ask24x0目录,并将board/100ask24x0/smdk2410.c改名为100ask24x0.c。
根据前面描述的配置过程可知,还要在include/configs目录下建立一个配置文件100ask24x0.h,可以将include/configs/smdk2410.h直接复制为100ask24x0.h。
还要修改两个Makefile,首先在顶层Makefile中增加如下两行:
100ask24x0_config:unconfig
@$(MKCONFIG)$(@:_config=)armarm920t100ask24x0NULLs3c24x0
然后在board/100ask24x0/Makefile中,如下修改(因为前面将smdk2410.c文件改名为100ask24x0.c了):
COBJS:=smdk2410.oflash.o
改为:
COBJS:=100ask24x0.oflash.o
(2)修改SDRAM的配置。
SDRAM的初始化在U-Boot的第一阶段完成,就是在board/100ask24x0/lowlevel_init.S文件中设置存储控制器。
检查一下BANK6的设置:位宽为32──宏B6_BWSCON刚好为DW32(表示32位),无需改变;另外还要根据HCLK设置SDRAM的刷新参数,主要是REFCNT寄存器。
本书所用开发板的HCLK都设为100MHz,需要根据SDRAM芯片的具体参数重新计算REFCNT寄存器的值(请参考第6章)。代码修改如下:
126#defineREFCNT1113/*period=15.6us,HCLK=60Mhz,(2048+1-15.6*60)*/
改为
126#defineREFCNT0x4f4/*period=7.8125us,HCLK=100Mhz,(2048+1-7.8125*100)*/
对于其他BANK,比如网卡芯片CS8900所在的BANK2,原来的设置刚好匹配,无需更改;而对于BANK1、2、4、5、7,在U-Boot中并没有使用到它们外接的设备,也不需要理会。
(3)增加对S3C2440的支持。
S3C2440是S3C2410的改进版,它们的操作基本相似。不过在系统时钟的设置、
NAND
Flash控制器的操作等方面,有一些小差别。它们的MPLL、UPLL计算公式不一样,FCLK、HCLK和PCLK的分频化设置也不一样,这在下面的
代码中可以看到。NANDFlash控制器的差别在增加对NANDFlash的支持时讲述。
本章的目标是令同一个U-Boot二进制代码既可以在S3C2410上运行,也可以在
S3C2440上运行。首先需要在代码中自动识别是S3C2410还是S3C2440,这可以通过读取GSTATUS1寄存器的值来分
辨:0x32410000表示S3C2410,0x32410002表示S3C2410A,0x32440000表示
S3C2440,0x32440001表示S3C2440A。S3C2410和S3C2410A、S3C2440和S3C2440A,对本书来说没有区
别。
对于S3C2410开发板,将FCLK设为200MHz,分频比为
FCLK:HCLK:PCLK=1:2:4;对于S3C2440开发板,将FCLK设为400MHz,分频比为
FCLK:HCLK:PCLK=1:4:8。还将UPLL设为48MHz,即UCLK为48MHz,以在内核中支持USB控制器。
首先修改board/100ask24x0/100ask24x0.c中的board_init函数,下面是修改后的代码:
33/*S3C2440:MPLL=(2*m*Fin)/(p*2^s),UPLL=(m*Fin)/(p*2^s)
34*m=M(thevaluefordividerM)+8,p=P(thevaluefordividerP)+2
35*/
36#defineS3C2440_MPLL_400MHZ((0x5c<<12)|(0x01<<4)|(0x01))
37#defineS3C2440_UPLL_48MHZ((0x38<<12)|(0x02<<4)|(0x02))
38#defineS3C2440_CLKDIV0x05/*FCLK:HCLK:PCLK=1:4:8,UCLK=UPLL*/
39
40/*S3C2410:Mpll,Upll=(m*Fin)/(p*2^s)
41*m=M(thevaluefordividerM)+8,p=P(thevaluefordividerP)+2
42*/
43#defineS3C2410_MPLL_200MHZ((0x5c<<12)|(0x04<<4)|(0x00))
44#defineS3C2410_UPLL_48MHZ((0x28<<12)|(0x01<<4)|(0x02))
45#defineS3C2410_CLKDIV0x03/*FCLK:HCLK:PCLK=1:2:4*/
46
上面几行针对S3C2410、S3C2440分别定义了MPLL、UPLL寄存器的值。开发
板输入时钟为12MHz(这在include/configs/100ask24x0.h中的宏CONFIG_SYS_CLK_FREQ中定义),读者可
以根据代码中的计算公式针对自己的开发板修改系统时钟。
下面是针对S3C2410、S3C2440,分别使用不同的宏设置系统时钟:
58intboard_init(void)
59{
60S3C24X0_CLOCK_POWER*constclk_power=S3C24X0_GetBase_CLOCK_POWER();
61S3C24X0_GPIO*constgpio=S3C24X0_GetBase_GPIO();
62
63/*设置GPIO*/
64gpio->GPACON=0x007FFFFF;
65gpio->GPBCON=0x00044555;
66gpio->GPBUP=0x000007FF;
67gpio->GPCCON=0xAAAAAAAA;
68gpio->GPCUP=0x0000FFFF;
69gpio->GPDCON=0xAAAAAAAA;
70gpio->GPDUP=0x0000FFFF;
71gpio->GPECON=0xAAAAAAAA;
72gpio->GPEUP=0x0000FFFF;
73gpio->GPFCON=0x000055AA;
74gpio->GPFUP=0x000000FF;
75gpio->GPGCON=0xFF95FFBA;
76gpio->GPGUP=0x0000FFFF;
77gpio->GPHCON=0x002AFAAA;
78gpio->GPHUP=0x000007FF;
79
80/*同时支持S3C2410和S3C2440,www.100ask.net*/
81if((gpio->GSTATUS1==0x32410000)||(gpio->GSTATUS1==0x32410002))
82{
83/*FCLK:HCLK:PCLK=1:2:4*/
84clk_power->CLKDIVN=S3C2410_CLKDIV;
85
86/*修改为异步总线模式*/
87__asm__("mrcp15,0,r1,c1,c0,0\n"/*readctrlregister*/
88"orrr1,r1,#0xc0000000\n"/*Asynchronous*/
89"mcrp15,0,r1,c1,c0,0\n"/*writectrlregister*/
90:::"r1"
91);
92
93/*设置PLL锁定时间*/
94clk_power->LOCKTIME=0xFFFFFF;
95
96/*配置MPLL*/
97clk_power->MPLLCON=S3C2410_MPLL_200MHZ;
98
99/*配置MPLL后,要延时一段时间再配置UPLL*/
100delay(4000);
101
102/*配置UPLL*/
103clk_power->UPLLCON=S3C2410_UPLL_48MHZ;
104
105/*再延时一会*/
106delay(8000);
107
108/*机器类型ID,这在调用Linux内核时用到*/
109gd->bd->bi_arch_number=MACH_TYPE_SMDK2410;
110}
111else
112{
113/*FCLK:HCLK:PCLK=1:4:8*/
114clk_power->CLKDIVN=S3C2440_CLKDIV;
115
116/*修改为异步总线模式*/
117__asm__("mrcp15,0,r1,c1,c0,0\n"/*readctrlregister*/
118"orrr1,r1,#0xc0000000\n"/*Asynchronous*/
119"mcrp15,0,r1,c1,c0,0\n"/*writectrlregister*/
120:::"r1"
121);
122
123/*设置PLL锁定时间*/
124clk_power->LOCKTIME=0xFFFFFF;
125
126/*配置MPLL*/
127clk_power->MPLLCON=S3C2440_MPLL_400MHZ;
128
129/*配置MPLL后,要延时一段时间再配置UPLL*/
130delay(4000);
131
132/*配置UPLL*/
133clk_power->UPLLCON=S3C2440_UPLL_48MHZ;
134
135/*再延时一会*/
136delay(8000);
137
138/*机器类型ID,这在调用Linux内核时用到,这个值要与内核相对应*/
139gd->bd->bi_arch_number=MACH_TYPE_S3C2440;
140}
141
142/*启动内核时,参数存放位置。这个值在构造标记列表时用到*/
143gd->bd->bi_boot_params=0x30000100;
144
145icache_enable();
146dcache_enable();
147
148return0;
149}
150
最后一步:获取系统时钟的函数需要针对S3C2410、S3C2440的不同进行修改。
在后面设置串口波特率时需要获得系统时钟,就是在U-Boot的第二阶
段,lib_arm/board.c中start_armboot函数调用serial_init函数初始化串口时,会调用get_PCLK函数。它在
cpu/arm920t/s3c24x0/speed.c中定义,与它相关的还有get_HCLK、get_PLLCLK等函数。
前面的board_init函数在识别出S3C2410或S3C2440后,设置了机器类型
ID:gd->bd->bi_arch_number,后面的函数可以通过它来分辨是S3C2410还是S3C2440。首先要在程序的开头
增加如下一行,这样才可以使用gd变量:
DECLARE_GLOBAL_DATA_PTR;
S3C2410和S3C2440的MPLL、UPLL计算公式不一样,所以get_PLLCLK函数也需要修改:
56staticulongget_PLLCLK(intpllreg)
57{
58S3C24X0_CLOCK_POWER*constclk_power=S3C24X0_GetBase_CLOCK_POWER();
59ulongr,m,p,s;
60
61if(pllreg==MPLL)
62r=clk_power->MPLLCON;
63elseif(pllreg==UPLL)
64r=clk_power->UPLLCON;
65else
66hang();
67
68m=((r&0xFF000)>>12)+8;
69p=((r&0x003F0)>>4)+2;
70s=r&0x3;
71
72/*同时支持S3C2410和S3C2440,bywww.100ask.net*/
73if(gd->bd->bi_arch_number==MACH_TYPE_SMDK2410)
74return((CONFIG_SYS_CLK_FREQ*m)/(p<<s));
75else
76return((CONFIG_SYS_CLK_FREQ*m*2)/(p<<s));/*S3C2440*/
77}
78
由于分频系数的设置方法也不一样,get_HCLK、get_PCLK也需要修改。对于S3C2410,沿用原来的计算方法,else分支中是S3C2440的代码:
85/*fors3c2440*/
86#defineS3C2440_CLKDIVN_PDIVN(1<<0)
87#defineS3C2440_CLKDIVN_HDIVN_MASK(3<<1)
88#defineS3C2440_CLKDIVN_HDIVN_1(0<<1)
89#defineS3C2440_CLKDIVN_HDIVN_2(1<<1)
90#defineS3C2440_CLKDIVN_HDIVN_4_8(2<<1)
91#defineS3C2440_CLKDIVN_HDIVN_3_6(3<<1)
92#defineS3C2440_CLKDIVN_UCLK(1<<3)
93
94#defineS3C2440_CAMDIVN_CAMCLK_MASK(0xf<<0)
95#defineS3C2440_CAMDIVN_CAMCLK_SEL(1<<4)
96#defineS3C2440_CAMDIVN_HCLK3_HALF(1<<8)
97#defineS3C2440_CAMDIVN_HCLK4_HALF(1<<9)
98#defineS3C2440_CAMDIVN_DVSEN(1<<12)
99
100/*returnHCLKfrequency*/
101ulongget_HCLK(void)
102{
103S3C24X0_CLOCK_POWER*constclk_power=S3C24X0_GetBase_CLOCK_POWER();
104unsignedlongclkdiv;
105unsignedlongcamdiv;
106inthdiv=1;
107
108/*同时支持S3C2410和S3C2440,bywww.100ask.net*/
109if(gd->bd->bi_arch_number==MACH_TYPE_SMDK2410)
110return((clk_power->CLKDIVN&0x2)?get_FCLK()/2:get_FCLK());
111else
112{
113clkdiv=clk_power->CLKDIVN;
114camdiv=clk_power->CAMDIVN;
115
116/*计算分频比*/
117
118switch(clkdiv&S3C2440_CLKDIVN_HDIVN_MASK){
119caseS3C2440_CLKDIVN_HDIVN_1:
120hdiv=1;
121break;
122
123caseS3C2440_CLKDIVN_HDIVN_2:
124hdiv=2;
125break;
126
127caseS3C2440_CLKDIVN_HDIVN_4_8:
128hdiv=(camdiv&S3C2440_CAMDIVN_HCLK4_HALF)?8:4;
129break;
130
131caseS3C2440_CLKDIVN_HDIVN_3_6:
132hdiv=(camdiv&S3C2440_CAMDIVN_HCLK3_HALF)?6:3;
133break;
134}
135
136returnget_FCLK()/hdiv;
137}
138}
139
140/*returnPCLKfrequency*/
141ulongget_PCLK(void)
142{
143S3C24X0_CLOCK_POWER*constclk_power=S3C24X0_GetBase_CLOCK_POWER();
144unsignedlongclkdiv;
145unsignedlongcamdiv;
146inthdiv=1;
147
148/*同时支持S3C2410和S3C2440,bywww.100ask.net*/
149if(gd->bd->bi_arch_number==MACH_TYPE_SMDK2410)
150return((clk_power->CLKDIVN&0x1)?get_HCLK()/2:get_HCLK());
151else
152{
153clkdiv=clk_power->CLKDIVN;
154camdiv=clk_power->CAMDIVN;
155
156/*计算分频比*/
157
158switch(clkdiv&S3C2440_CLKDIVN_HDIVN_MASK){
159caseS3C2440_CLKDIVN_HDIVN_1:
160hdiv=1;
161break;
162
163caseS3C2440_CLKDIVN_HDIVN_2:
164hdiv=2;
165break;
166
167caseS3C2440_CLKDIVN_HDIVN_4_8:
168hdiv=(camdiv&S3C2440_CAMDIVN_HCLK4_HALF)?8:4;
169break;
170
171caseS3C2440_CLKDIVN_HDIVN_3_6:
172hdiv=(camdiv&S3C2440_CAMDIVN_HCLK3_HALF)?6:3;
173break;
174}
175
176returnget_FCLK()/hdiv/((clkdiv&S3C2440_CLKDIVN_PDIVN)?2:1);
177}
178}
179
现在重新执行“make100ask24x0_config”和“make
all”生成的u-boot.bin文件既可以运行于S3C2410开发板,也可以运行于S3C2440开发板。将它烧入NOR
Flash后启动,就可以在串口工具(设置为115200,8N1)中看到提示信息,可以输入各种命令操作U-Boot了。
(4)选择NORFlash的型号。
但是,现在还无法通过U-Boot命令烧写NORFlash。本书所用开发板中的NORFlash型号为AM29LV800,而配置文件include/configs/100ask24x0.h中的默认型号为AM29LV400。修改如下:
#defineCONFIG_AMD_LV4001/*uncommentthisifyouhaveaLV400flash*/
#if0
#defineCONFIG_AMD_LV8001/*uncommentthisifyouhaveaLV800flash*/
#endif
改为:
#if0
#defineCONFIG_AMD_LV4001/*uncommentthisifyouhaveaLV400flash*/
#endif
#defineCONFIG_AMD_LV8001/*uncommentthisifyouhaveaLV800flash*/
本例中NOR
Flash的操作函数在board/100ask24x0/flash.c中实现,它支持AM29LV400y和AM29LV800。对于其他型号的
NOR
Flash,如果符合CFI接口标准,则可以在使用drivers/cfi_flash.c中的接口函数;否则,只好自己编写了。如果要使用
cfi_flash.c,如下修改两个文件:
在include/configs/100ask24x0.h中增加以下一行:
#defineCFG_FLASH_CFI_DRIVER1
在board/100ask24x0/Makefile中去掉flash.o:
COBJS:=100ask24x0.oflash.o
改为:
COBJS:=100ask24x0.o
修改好对NORFlash的支持后,重新编译U-Boot:makeclean、makeall。运行后可以在串口中看到如下字样:
Flash:1MB
现在可以使用loadb、loady等命令通过串口下载文件,然后使用erase、cp命令分别擦除、烧写NORFlash了,它们的效率比JTAG快上好几倍。
2.支持串口xmodem协议
上面的loadb命令需要配合Linux下的kermit工具来使用,loady命令通过串
口ymodem协议来传输文件。Windows下的超级终端虽然支持ymodem,但是它的使用界面实在不友好。而本书推荐使用的Windows工具
SecureCRT只支持xmodem和zmodem。为了方便在Windows下开发,现在修改代码增加对xmodem的支持,即增加一个命令
loadx。
依照loady的实现来编写代码,首先使用U_BOOT_CMD宏来增加loadx命令:
/*支持xmodem,www.100ask.net*/
U_BOOT_CMD(
loadx,3,0,do_load_serial_bin,
"loadx-loadbinaryfileoverserialline(xmodemmode)\n",
"[off][baud]\n"
"-loadbinaryfileoverserialline"
"withoffset'off'andbaudrate'baud'\n"
);
其次,在do_load_serial_bin函数中增加对loadx命令的处理分支。也是依照loady来实现:
481/*支持xmodem,www.100ask.net*/
482if(strcmp(argv[0],"loadx")==0){
483printf("##Readyforbinary(xmodem)download"
484"to0x%08lXat%dbps...\n",
485offset,
486load_baudrate);
487
488addr=load_serial_xmodem(offset);
489
490}elseif(strcmp(argv[0],"loady")==0){
491printf("##Readyforbinary(ymodem)download"
492"to0x%08lXat%dbps...\n",
……
第481~490行就是为loadx命令增加的代码。
在第288行调用load_serial_xmodem函数,它是依照load_serial_ymodem实现的一个新函数:
36#if(CONFIG_COMMANDS&CFG_CMD_LOADB)
37/*支持xmodem,www.100ask.net*/
38staticulongload_serial_xmodem(ulongoffset);
39staticulongload_serial_ymodem(ulongoffset);
40#endif
……
995/*支持xmodem,www.100ask.net*/
996staticulongload_serial_xmodem(ulongoffset)
997{
……
1003charxmodemBuf[1024];/*原来是ymodemBuf,这只是为了与函数名称一致*/
……
1008info.mode=xyzModem_xmodem;/*原来是xyzModem_ymodem,对应ymodem*/
……
首先在文件开头增加load_serial_xmodem函数的声明,然后复制load_serial_ymodem函数为load_serial_xmodem,稍作修改:
①将局部数组ymodemBuf改名为xmodemBuf,并在后面使用到的地方统一修改。这只是为了与函数名称一致。
②info.mode的值从xyzModem_ymodem改为xyzModem_xmodem。
重新编译、烧写u-boot.bin后,就可以使用loadx命令下载文件了。
3.支持网卡芯片CS8900
使用串口来传输文件的速率太低,现在增加对网卡芯片CS8900的支持。
本书使用开发板的网卡芯片CS8900的连接方式与smdk2410完全一样,所以现在的
U-Boot中已经支持CS8900了,它的驱动程序为drivers/cs8900.c。只要在U-Boot控制界面中稍加配置就可以使用网络功能。使
用网络之前,先设置开发板IP地址、MAC地址,服务器IP地址,比如可以在U-Boot中执行以下命令:
setenvipaddr192.168.1.17
setenvethaddr08:00:3e:26:0a:5b
setenvserverip192.168.1.11
saveenv
然后就可以使用tftp或nfs命令下载文件了,注意:服务器上要开启tftp或nfs服务。比如可以使用如下命令将u-boot.bin文件下载到内存0x30000000中:
tftp0x30000000u-boot.bin
或
nfs0x30000000192.168.1.57:/work/nfs_root/u-boot.bin
可以修改配置文件,让网卡的各个默认值就是上面设置的值。在此之前,先了解网卡的相关文件,这有助于移植代码以支持其他连接方式的CS8900。
首先,CS8900接在S3C2410、S3C2440的BANK3,位宽为16,使用WAIT、nBE信号。在设置存储控制器时要设置好BANK3。代码在board/100ask24x0/lowlevel_init.S中:
#defineB3_BWSCON(DW16+WAIT+UBLB)
……
/*时序参数*/
#defineB3_Tacs0x0/*0clk*/
#defineB3_Tcos0x3/*4clk*/
#defineB3_Tacc0x7/*14clk*/
#defineB3_Tcoh0x1/*1clk*/
#defineB3_Tah0x0/*0clk*/
#defineB3_Tacp0x3/*6clk*/
#defineB3_PMC0x0/*normal*/
接下来,还要确定CS8900的基地址。这在配置文件include/configs/100ask24x0.h中定义:
#defineCONFIG_DRIVER_CS89001/*使用CS8900*/
#defineCS8900_BASE0x19000300/*基地址*/
#defineCS8900_BUS161/*位宽为16*/
从第6章可以知道网卡CS8900的访问基址为0x19000000,之所以再偏移0x300是由它的特性决定的。
最后,还是在配置文件include/configs/100ask24x0.h中定义CS8900的各个默认地址:
#defineCONFIG_ETHADDR08:00:3e:26:0a:5b
#defineCONFIG_NETMASK255.255.255.0
#defineCONFIG_IPADDR192.168.1.17
#defineCONFIG_SERVERIP192.168.1.11
额外的,如果要增加ping命令,还可以在配置文件include/configs/100ask24x0.h的宏CONFIG_COMMANDS中增加CFG_CMD_PING,如下:
#defineCONFIG_COMMANDS\
(CONFIG_CMD_DFL|\
CFG_CMD_CACHE|\
CFG_CMD_PING|\
……
4.支持NANDFlash
U-Boot1.1.6中对NAND
Flash的支持有新旧两套代码,新代码在drivers/nand目录下,旧代码在drivers/nand_legacy目录下。文档doc
/README.nand对这两套代码有所说明:使用旧代码需要定义更多的宏,而新代码移植自Linux内核2.6.12,它更加智能,可以自动识别更多
型号的NAND
Flash。目前之所以还保留旧的代码,是因为两个目标板NETTA、NETTA_ISDN使用JFFS文件系统,它们还依赖于旧代码。当相关功能移植到
新代码之后,旧的代码将从U-Boot中去除。
要让U-Boot支持NANDFlash,首先在配置文件include/configs/100ask24x0.h的宏CONFIG_COMMANDS中增加CFG_CMD_NAND,如下:
#defineCONFIG_COMMANDS\
(CONFIG_CMD_DFL|\
CFG_CMD_CACHE|\
CFG_CMD_PING|\
CFG_CMD_NAND|\
……
然后选择使用哪套代码:在配置文件中定义宏CFG_NAND_LEGACY则使用旧代码,否则使用新代码。
使用旧代码时,需要实现drivers/nand_legacy/nand_legacy.c中使用到的各种宏,比如:
#defineNAND_WAIT_READY(nand)/*等待NandFlash的状态为“就绪”,代码依赖于具体的开发板*/
#defineWRITE_NAND_COMMAND(d,adr)/*写NANDFlash命令,代码依赖于具体的开发板*/
本书使用新代码,下面讲述移植过程。
代码的移植没有现成的文档,可以在配置文件include/configs/100ask24x0.h的宏CONFIG_COMMANDS中增加CFG_CMD_NAND后,就编译代码,然后一个一个地解决出现的错误。编译结果中出现的错误和警告如下:
nand.h:412:error:`NAND_MAX_CHIPS'undeclaredhere(notinafunction)
nand.c:35:error:`CFG_MAX_NAND_DEVICE'undeclaredhere(notinafunction)
nand.c:38:error:`CFG_NAND_BASE'undeclaredhere(notinafunction)
nand.c:35:error:storagesizeof`nand_info'isn'tknown
nand.c:37:error:storagesizeof`nand_chip'isn'tknown
nand.c:38:error:storagesizeof`base_address'isn'tknown
nand.c:37:warning:'nand_chip'definedbutnotused
nand.c:38:warning:'base_address'definedbutnotused
在配置文件include/configs/100ask24x0.h中增加如下3个宏就可
以解决上述错误。在Flash的驱动程序中,设备是逻辑上的概念,表示一组相同结构、访问函数相同的Flash芯片。在本书所用开发板中,只有一个
NANDFlash芯片,所以设备数为1,芯片数也为1。
#defineCFG_NAND_BASE0/*无实际意义:基地址,这在board_nand_init中重新指定*/
#defineCFG_MAX_NAND_DEVICE1/*NANDFlash“设备”的数目为1*/
#defineNAND_MAX_CHIPS1/*每个NANDFlash“设备”由1个NANDFlash“芯片”组成*/
修改配置文件后再次编译,现在只有一个错误了,“board_nand_init函数未定义”:
nand.c:50:undefinedreferenceto`board_nand_init'
调用board_nand_init函数的过程为:NAND
Flash的初始化入口函数是nand_init,它在lib_arm/board.c的start_armboot函数中被调用;nand_init函
数在drivers/nand/nand.c中实现,它调用相同文件中的nand_init_chip函数;nand_init_chip函数首先调用
board_nand_init函数来初始化NANDFlash设备,最后才是统一的识别过程。
从board_nand_init函数的名称就可以知道它是平台/开发板相关的函数,需要自
己编写。本书在cpu/arm920t/s3c24x0目录下新建一个文件nand_flash.c,在里面针对S3C2410、S3C2440实现了统
一的board_nand_init函数。
在编写board_nand_init函数的之前,需要针对S3C2410、S3C2440NANDFlash控制器的不同定义一些数据结构和函数:
(1)在include/s3c24x0.h文件中增加S3C2440_NAND数据结构。
/*NANDFLASH(seeS3C2440manualchapter6,www.100ask.net)*/
typedefstruct{
S3C24X0_REG32NFCONF;
S3C24X0_REG32NFCONT;
S3C24X0_REG32NFCMD;
S3C24X0_REG32NFADDR;
S3C24X0_REG32NFDATA;
S3C24X0_REG32NFMECCD0;
S3C24X0_REG32NFMECCD1;
S3C24X0_REG32NFSECCD;
S3C24X0_REG32NFSTAT;
S3C24X0_REG32NFESTAT0;
S3C24X0_REG32NFESTAT1;
S3C24X0_REG32NFMECC0;
S3C24X0_REG32NFMECC1;
S3C24X0_REG32NFSECC;
S3C24X0_REG32NFSBLK;
S3C24X0_REG32NFEBLK;
}/*__attribute__((__packed__))*/S3C2440_NAND;
(2)在include/s3c2410.h文件中仿照S3C2410_GetBase_NAND函数定义S3C2440_GetBase_NAND函数。
/*fors3c2440,www.100ask.net*/
staticinlineS3C2440_NAND*constS3C2440_GetBase_NAND(void)
{
return(S3C2440_NAND*const)S3C2410_NAND_BASE;
}
既然新的NAND
Flash代码是从Linux内核2.6.12中移植来的,那么cpu/arm920t/s3c24x0/nand_flash.c文件也可以仿照内核
中,对S3C2410、S3C2440的NAND
Flash进行初始化的drivers/mtd/nand/s3c2410.c文件来编写。为了方便阅读,先把cpu/arm920t/s3c24x0
/nand_flash.c文件的代码全部列出来,再讲解:
01/*
02*s3c2410/s3c2440的NANDFlash控制器接口,www.100ask.net
03*修改自Linux内核2.6.13文件drivers/mtd/nand/s3c2410.c
04*/
05
06#include<common.h>
07
08#if(CONFIG_COMMANDS&CFG_CMD_NAND)&&!defined(CFG_NAND_LEGACY)
09#include<s3c2410.h>
10#include<nand.h>
11
12DECLARE_GLOBAL_DATA_PTR;
13
14#defineS3C2410_NFSTAT_READY(1<<0)
15#defineS3C2410_NFCONF_nFCE(1<<11)
16
17#defineS3C2440_NFSTAT_READY(1<<0)
18#defineS3C2440_NFCONT_nFCE(1<<1)
19
20
21/*S3C2410:NANDFlash的片选函数*/
22staticvoids3c2410_nand_select_chip(structmtd_info*mtd,intchip)
23{
24S3C2410_NAND*consts3c2410nand=S3C2410_GetBase_NAND();
25
26if(chip==-1){
27s3c2410nand->NFCONF|=S3C2410_NFCONF_nFCE;/*禁止片选信号*/
28}else{
29s3c2410nand->NFCONF&=~S3C2410_NFCONF_nFCE;/*使能片选信号*/
30}
31}
32
33/*S3C2410:命令和控制函数
34*
35*注意,这个函数仅仅根据各种命令来修改“写地址”IO_ADDR_W的值(这称为tglx方法),
36*这种方法使得平台/开发板相关的代码很简单。
37*真正发出命令是在上一层NANDFlash的统一的驱动中实现,
38*它首先调用这个函数修改“写地址”,然后才分别发出控制、地址、数据序列。
39*/
40staticvoids3c2410_nand_hwcontrol(structmtd_info*mtd,intcmd)
41{
42S3C2410_NAND*consts3c2410nand=S3C2410_GetBase_NAND();
43structnand_chip*chip=mtd->priv;
44
45switch(cmd){
46caseNAND_CTL_SETNCE:
47caseNAND_CTL_CLRNCE:
48printf("%s:calledforNCE\n",__FUNCTION__);
49break;
50
51caseNAND_CTL_SETCLE:
52chip->IO_ADDR_W=(void*)&s3c2410nand->NFCMD;
53break;
54
55caseNAND_CTL_SETALE:
56chip->IO_ADDR_W=(void*)&s3c2410nand->NFADDR;
57break;
58
59/*NAND_CTL_CLRCLE:*/
60/*NAND_CTL_CLRALE:*/
61default:
62chip->IO_ADDR_W=(void*)&s3c2410nand->NFDATA;
63break;
64}
65}
66
67/*S3C2410:查询NANDFlash状态
68*
69*返回值:0–忙,1–就绪
70*/
71staticints3c2410_nand_devready(structmtd_info*mtd)
72{
73S3C2410_NAND*consts3c2410nand=S3C2410_GetBase_NAND();
74
75return(s3c2410nand->NFSTAT&S3C2410_NFSTAT_READY);
76}
77
78
79/*S3C2440:NANDFlash的片选函数*/
80staticvoids3c2440_nand_select_chip(structmtd_info*mtd,intchip)
81{
82S3C2440_NAND*consts3c2440nand=S3C2440_GetBase_NAND();
83
84if(chip==-1){
85s3c2440nand->NFCONT|=S3C2440_NFCONT_nFCE;/*禁止片选信号*/
86}else{
87s3c2440nand->NFCONT&=~S3C2440_NFCONT_nFCE;/*使能片选信号*/
88}
89}
90
91/*S3C2440:命令和控制函数,与s3c2410_nand_hwcontrol函数类似*/
92staticvoids3c2440_nand_hwcontrol(structmtd_info*mtd,intcmd)
93{
94S3C2440_NAND*consts3c2440nand=S3C2440_GetBase_NAND();
95structnand_chip*chip=mtd->priv;
96
97switch(cmd){
98caseNAND_CTL_SETNCE:
99caseNAND_CTL_CLRNCE:
100printf("%s:calledforNCE\n",__FUNCTION__);
101break;
102
103caseNAND_CTL_SETCLE:
104chip->IO_ADDR_W=(void*)&s3c2440nand->NFCMD;
105break;
106
107caseNAND_CTL_SETALE:
108chip->IO_ADDR_W=(void*)&s3c2440nand->NFADDR;
109break;
110
111/*NAND_CTL_CLRCLE:*/
112/*NAND_CTL_CLRALE:*/
113default:
114chip->IO_ADDR_W=(void*)&s3c2440nand->NFDATA;
115break;
116}
117}
118
119/*S3C2440:查询NANDFlash状态
120*
121*返回值:0–忙,1–就绪
122*/
123staticints3c2440_nand_devready(structmtd_info*mtd)
124{
125S3C2440_NAND*consts3c2440nand=S3C2440_GetBase_NAND();
126
127return(s3c2440nand->NFSTAT&S3C2440_NFSTAT_READY);
128}
129
130/*
131*Nandflash硬件初始化:
132*设置NANDFlash的时序,使能NANDFlash控制器
133*/
134staticvoids3c24x0_nand_inithw(void)
135{
136S3C2410_NAND*consts3c2410nand=S3C2410_GetBase_NAND();
137S3C2440_NAND*consts3c2440nand=S3C2440_GetBase_NAND();
138
139#defineTACLS0
140#defineTWRPH04
141#defineTWRPH12
142
143if(gd->bd->bi_arch_number==MACH_TYPE_SMDK2410)
144{
145/*使能NANDFlash控制器,初始化ECC,使能片选信号,设置时序*/
146s3c2410nand->NFCONF=(1<<15)|(1<<12)|(1<<11)|(TACLS<<8)|(TWRPH0<<4)|(TWRPH1<<0);
147}
148else
149{
150/*设置时序*/
151s3c2440nand->NFCONF=(TACLS<<12)|(TWRPH0<<8)|(TWRPH1<<4);
152/*初始化ECC,使能NANDFlash控制器,使能片选信号*/
153s3c2440nand->NFCONT=(1<<4)|(0<<1)|(1<<0);
154}
155}
156
157/*
158*被drivers/nand/nand.c调用,初始化NANDFlash硬件,初始化访问接口函数
159*/
160voidboard_nand_init(structnand_chip*chip)
161{
162S3C2410_NAND*consts3c2410nand=S3C2410_GetBase_NAND();
163S3C2440_NAND*consts3c2440nand=S3C2440_GetBase_NAND();
164
165s3c24x0_nand_inithw();/*Nandflash硬件初始化*/
166
167if(gd->bd->bi_arch_number==MACH_TYPE_SMDK2410){
168chip->IO_ADDR_R=(void*)&s3c2410nand->NFDATA;
169chip->IO_ADDR_W=(void*)&s3c2410nand->NFDATA;
170chip->hwcontrol=s3c2410_nand_hwcontrol;
171chip->dev_ready=s3c2410_nand_devready;
172chip->select_chip=s3c2410_nand_select_chip;
173chip->options=0;/*设置位宽等,位宽为8*/
174}else{
175chip->IO_ADDR_R=(void*)&s3c2440nand->NFDATA;
176chip->IO_ADDR_W=(void*)&s3c2440nand->NFDATA;
177chip->hwcontrol=s3c2440_nand_hwcontrol;
178chip->dev_ready=s3c2440_nand_devready;
179chip->select_chip=s3c2440_nand_select_chip;
180chip->options=0;/*设置位宽等,位宽为8*/
181}
182
183chip->eccmode=NAND_ECC_SOFT;/*ECC较验方式:软件ECC*/
184}
185
186#endif
文件中分别针对S3C2410、S3C2440实现了NAND
Flash最底层访问函数,并进行了一些硬件的设置(比如时序、使能NANDFlash控制器等)。新的代码对NAND
Flash的封装做得很好,只要向上提供底层初始化函数board_nand_init来设置好平台/开发板相关的初始化、提供底层接口即可。
最后,只要将新建的nand_flash.c文件编入U-Boot中就可以擦除、读写NANDFlash了。如下修改cpu/arm920t/s3c24x0/Makefile文件即可:
COBJS=i2c.ointerrupts.oserial.ospeed.o\
usb_ohci.o
改为:
COBJS=i2c.ointerrupts.oserial.ospeed.o\
usb_ohci.onand_flash.o
现在,可以使用新编译的u-boot.bin烧写内核映像到NANDFlash去了,请参考15.2.6。
5.支持烧写yaffs文件系统映像
在实际生产中,可以通过烧片器等手段将内核、文件系统映像烧入固态存储设备中,Bootloader不需要具备烧写功能。但为了方便开发,通常在Bootloader中增加烧写内核、文件系统映像文件的功能。
增加了NANDFlash功能的U-Boot1.1.6已经可以通过“nand
write……”、“nandwrite.jffs2……”等命令来烧写内核,cramfs、jffs2文件系统映像文件。但是在NAND
Flash上,yaffs文件系统的性能更佳,下面增加“nandwrite.yaffs……”命令以烧写yaffs文件系统映像文件。
“nandwrite.yaffs……”字样的命令中,“nand”是具体命令,“write.yaffs……”是参数。nand命令在common/cmd_nand.c中实现:
U_BOOT_CMD(nand,5,1,do_nand,
"nand-NANDsub-system\n",
"info-showavailableNANDdevices\n"
"nanddevice[dev]-showorsetcurrentdevice\n"
"nandread[.jffs2]-addroff|partitionsize\n"
"nandwrite[.jffs2]-addroff|partitonsize-read/write`size'bytesstarting\n"
"atoffset`off'to/frommemoryaddress`addr'\n"
……
先在其中增加“nandwrite.yaffs……”的使用说明:
U_BOOT_CMD(nand,5,1,do_nand,
"nand-NANDsub-system\n",
"info-showavailableNANDdevices\n"
"nanddevice[dev]-showorsetcurrentdevice\n"
"nandread[.jffs2]-addroff|partitionsize\n"
"nandwrite[.jffs2]-addroff|partitonsize-read/write`size'bytesstarting\n"
"atoffset`off'to/frommemoryaddress`addr'\n"
"nandread.yaffsaddroffsize-readthe`size'byteyaffsimagestarting\n"
"atoffset`off'tomemoryaddress`addr'\n"
"nandwrite.yaffsaddroffsize-writethe`size'byteyaffsimagestarting\n"
"atoffset`off'frommemoryaddress`addr'\n"
……
然后,在nand命令的处理函数do_nand中增加对“write.yaffs……”的支持。do_nand函数仍在common/cmd_nand.c中实现,代码修改如下:
331(!strcmp(s,".jffs2")||!strcmp(s,".e")||!strcmp(s,".i"))){
……
354}elseif(s!=NULL&&!strcmp(s,".yaffs")){
355if(read){
356/*read*/
357nand_read_options_topts;
358memset(&opts,0,sizeof(opts));
359opts.buffer=(u_char*)addr;
360opts.length=size;
361opts.offset=off;
362opts.readoob=1;
363opts.quiet=quiet;
364ret=nand_read_opts(nand,&opts);
365}else{
366/*write*/
367nand_write_options_topts;
368memset(&opts,0,sizeof(opts));
369opts.buffer=(u_char*)addr;/*yaffs文件系统映像存放的地址*/
370opts.length=size;/*长度*/
371opts.offset=off;/*要烧写到的NANDFlash的偏移地址*/
372/*opts.forceyaffs=1;*//*计算ECC码的方法,没有使用*/
373opts.noecc=1;/*不需要计算ECC,yaffs映像中有OOB数据*/
374opts.writeoob=1;/*写OOB区*/
375opts.blockalign=1;/*每个“逻辑上的块”大小为1个“物理块”*/
376opts.quiet=quiet;/*是否打印提示信息*/
377opts.skipfirstblk=1;/*跳过第一个可用块*/
378ret=nand_write_opts(nand,&opts);
379}
380}else{
……
385}
386
第354~379行就是针对命令“nandread.yaffs……”、“nand
write.yaffs……”增加的代码。有兴趣的读者可以自己分析“if(read)”分支的代码,下面只讲解“else”分支,即“nand
write.yaffs……”命令的实现。
NANDFlash每一页大小为(512+16)字节(还有其他格式的NAND
Flash,比如每页大小为(256+8)、(2048+64)等),其中的512字节就是一般存储数据的区域,16字节称为OOB(OutOf
Band)区。通常在OOB区存放坏块标记、前面512字节的ECC较验码等。
cramfs、jffs2文件系统映像文件中并没有OOB区的内容,如果将它们烧入NOR
Flash中,则是简单的“平铺”关系;如果将它们烧入NANDFlash中,则NAND
Flash的驱动程序首先根据OOB的标记略过坏块,然后将一页数据(512字节)写入后,还会计算这512字节的ECC较验码,最后将它写入OOB区,
如此循环。cramfs、jffs2文件系统映像文件的大小通常是512的整数倍。
而yaffs文件系统映像文件的格式则跟它们不同,文件本身就包含了OOB区的数据(里面有
坏块标记、ECC较验码、其他yaffs相关的信息)。所以烧写时,不需要再计算ECC值,首先检查是否坏块(是则跳过),然后写入512字节的数据,最
后写入16字节的OOB数据,如此循环。yaffs文件系统映像文件的大小是(512+16)的整数倍。
注意:烧写yaffs文件系统映像时,分区上第一个可用的(不是坏块)块也要跳过。
下面分析上面的代码。
第369~371行设置源地址、目的地址、长度。烧写yaffs文件系统映像前,一般通过网
络将它下载到内存某个地址处(比如0x30000000),然后通过类似“nandwrite.yaffs0x30000000
0x00A00000$(filesize)”的命令烧到NAND
Flash的偏移地址0x00A00000处。对于这个命令,第369行中opts.buffer等于0x30000000,第370行中
opts.length等于$(filesize)的值,就是前面下载的文件的大小,第371行中的opts.offset等于0x00A00000。
这里列出不使用的第372行,是因为opts.forceyaffs这个名字很有欺骗性,它其实是指计算ECC较验码的一种方法。烧写yaffs文件系统映像时,不需要计算ECC较验码。
第373、374行指定烧写数据时不计算ECC较验码、而是烧入文件中的OOB数据。
第375行指定“逻辑块”的大小,“逻辑块”可以由多个“物理块”组成,在yaffs文件系统映像中,它们是1:1的关系。
第377行的opts.skipfirstblk是新加的项,nand_write_options_t结构中没有skipfirstblk成员。它表示烧写时跳过第一个可用的逻辑块──这是由yaffs文件系统的特性决定的。
既然skipfirstblk是在nand_write_options_t结构中新加的项,那么就要重新定义nand_write_options_t结构,并在下面调用的nand_write_opts函数中对它进行处理。
首先在include/nand.h中如下修改,增加skipfirstblk成员:
structnand_write_options{
u_char*buffer;/*memoryblockcontainingimagetowrite*/
ulonglength;/*numberofbytestowrite*/
ulongoffset;/*startaddressinNAND*/
intquiet;/*don'tdisplayprogressmessages*/
intautoplace;/*iftrueuseautoooblayout*/
intforcejffs2;/*forcejffs2ooblayout*/
intforceyaffs;/*forceyaffsooblayout*/
intnoecc;/*writewithoutecc*/
intwriteoob;/*imagecontainsoobdata*/
intpad;/*padtopagesize*/
intblockalign;/*1|2|4setmultipleoferaseblockstoalignto*/
intskipfirstblk;/*新加,烧写时跳过第一个可用的逻辑块*/
};
typedefstructnand_write_optionsnand_write_options_t;
然后,修改nand_write_opts函数增加对skipfirstblk成员的支持。它在drivers/nand/nand_util.c文件中,下面的第301、第430~435行是新加的:
285intnand_write_opts(nand_info_t*meminfo,constnand_write_options_t*opts)
286{
……
300intresult;
301intskipfirstblk=opts->skipfirstblk;
……
430/*skipthefirstgoodblockwhenwirteyaffsimage,bywww.100ask.net*/
431if(skipfirstblk){
432mtdoffset+=erasesize_blockalign;
433skipfirstblk=0;
434continue;
435}
……
进行了上面的移植后,U-Boot已经可以烧yaffs文件系统映像了。由于前面设置“opts.noecc=1”不使用ECC较验码,在烧写过程中会出现很多的提示信息:
WritingdatawithoutECCtoNAND-FLASHisnotrecommended
可以修改drivers/nand/nand_base.c文件的nand_write_page函数将它去掉:
917caseNAND_ECC_NONE:
918printk(KERN_WARNING"WritingdatawithoutECCtoNAND-FLASHisnotrecommended\n");
改为:
917caseNAND_ECC_NONE:
918//printk(KERN_WARNING"WritingdatawithoutECCtoNAND-FLASHisnotrecommended\n");
6.修改默认配置参数以方便使用
前面移植网卡芯片CS8900时,已经设置过默认IP地址等。为了使用U-Boot时减少一些设置,现在修改配置文件include/configs/100ask24x0.h增加默认配置参数,其中一些在移植过程中已经增加的选项这里也再次说明。
(1)Linux启动参数。
增加如下3个宏:
#defineCONFIG_SETUP_MEMORY_TAGS1/*向内核传递内存分布信息*/
#defineCONFIG_CMDLINE_TAG1/*向内核传递命令行参数*/
/*默认命令行参数*/
#defineCONFIG_BOOTARGS"noinitrdroot=/dev/mtdblock2init=/linuxrcconsole=ttySAC0"
(2)自动启动命令。
增加如下2个宏:
/*自动启动前延时3秒*/
#defineCONFIG_BOOTDELAY3
/*自动启动的命令*/
#defineCONFIG_BOOTCOMMAND“nboot0x3200000000;bootm0x32000000”
自动启动时(开机3秒内无输入),首先执行“nboot0x3200000000”命令将第0个NANDFlash偏移地址0上的映像文件复制到内存0x32000000中;然后执行“bootm0x32000000”命令启动内存中的映像。
(3)默认网络设置。
根据具体网络环境增加、修改下面4个宏:
#defineCONFIG_ETHADDR08:00:3e:26:0a:5b
#defineCONFIG_NETMASK255.255.255.0
#defineCONFIG_IPADDR192.168.1.17
#defineCONFIG_SERVERIP192.168.1.11
2.6U-Boot的常用命令
1.U-Boot的常用命令的用法
进入U-Boot控制界面后,可以运行各种命令,比如下载文件到内存,擦除、读写Flash,运行内存、NORFlash、NANDFlash中的程序,查看、修改、比较内存中的数据等。
使用各种命令时,可以使用其开头的若干个字母代替它。比如tftpboot命令,可以使用t、tf、tft、tftp等字母代替,只要其他命令不以这些字母开头即可。
当运行一个命令之后,如果它是可重复执行的(代码中使用U_BOOT_CMD定义这个命令时,第3个参数是1),若想再次运行可以直接输入回车。
U-Boot接受的数据都是16进制,输入时可以省略前缀0x、0X。
下面介绍常用的命令:
(1)帮助命令help。
运行help命令可以看到U-Boot中所有命令的作用,如果要查看某个命令的使用方法,运行“help命令名”,比如“helpbootm”。
可以使用“?”来代替“help”,比如直接输入“?”、“?bootm”。
(2)下载命令。
U-Boot支持串口下载、网络下载,相关命令有:loadb、loads、loadx、loady和tftpboot、nfs。
前几个串口下载命令使用方法相似,以loadx命令为例,它的用法为“loadx[off][baud
]”。中括号“[]”表示里面的参数可以省略,off表示文件下载后存放的内存地址,baud表示使用的波特率。如果baud参数省略,则使用当前的波特
率;如果off参数省略,存放的地址为配置文件中定义的宏CFG_LOAD_ADDR。
tftpboot命令使用TFTP协议从服务器下载文件,服务器的IP地址为环境变量
serverip。用法为“tftpboot[loadAddress]
[bootfilename]”,loadAddress表示文件下载后存放的内存地址,bootfilename表示要下载的文件的名称。如果
loadAddress省略,存放的地址为配置文件中定义的宏CFG_LOAD_ADDR;如果bootfilename省略,则使用单板的IP地址构造
一个文件名,比如单板IP为192.168.1.17,则缺省的文件名为C0A80711.img。
nfs命令使用NFS协议下载文件,用法为“nfs[loadAddress]
[hostip
addr:bootfilename]”。loadAddress、bootfilename的意义与tftpboot命令一样,hostip
addr表示服务器的IP地址,默认为环境变量serverip。
下载文件成功后,U-Boot会自动创建或更新环境变量filesize,它表示下载的文件的长度,可以在后续命令中使用“$(filesize)”来引用它。
(3)内存操作命令。
常用的命令有:查看内存命令md、修改内存命令md、填充内存命令mw、拷贝命令cp。这些
命令都可以带上后缀“.b”、“.w”或“.l”,表示以字节、字(2个字节)、双字(4个字节)为单位进行操作。比如“cp.l30000000
310000002”将从开始地址0x30000000处,拷贝2个双字到开始地址为0x31000000的地方。
md命令用法为“md[.b,.w,.l]address[count]”,表示以字节、字或双字(默认为双字)为单位,显示从地址address开始的内存数据,显示的数据个数为count。
mm命令用法为“mm[.b,.w,.l]address”,表示以字节、字或双字(默认为双字)为单位,从地址address开始修改内存数据。执行mm命令后,输入新数据后回车,地址会自动增加,Ctrl+C退出。
mw命令用法为“mw[.b,.w,.l]addressvalue[count]”,表示以字节、字或双字(默认为双字)为单位,往开始地址为address的内存中填充count个数据,数据值为value。
cp命令用法为“cp[.b,.w,.l]sourcetargetcount”,表示以字节、字或双字(默认为双字)为单位,从源地址source的内存拷贝count个数据到目的地址的内存。
(4)NORFlash操作命令。
常用的命令有查看Flash信息的flinfo命令、加/解写保护命令protect、擦除
命令erase。由于NORFlash的接口与一般内存相似,所以一些内存命令可以在NORFlash上使用,比如读NOR
Flash时可以使用md、cp命令,写NORFlash时可以使用cp命令(cp根据地址分辨出是NORFlash,从而调用NOR
Flash驱动完成写操作)。
直接运行“flinfo”即可看到NORFlash的信息,有NORFlash的型号、容量、各扇区的开始地址、是否只读等信息。比如对于本书基于的开发板,flinfo命令的结果如下:
Bank#1:AMD:1xAmd29LV800BB(8Mbit)
Size:1MBin19Sectors
SectorStartAddresses:
00000000(RO)00004000(RO)00006000(RO)00008000(RO)00010000(RO)
00020000(RO)00030000000400000005000000060000
000700000008000000090000000A0000000B0000
000C0000000D0000000E0000000F0000(RO)
其中的RO表示该扇区处于写保护状态,只读。
对于只读的扇区,在擦除、烧写它之前,要先解除写保护。最简单的命令为“protectoffall”,解除所有NORFlash的写保护。
erase命令常用的格式为“erasestart
end”──擦除的地址范围为start至end、“erasestart+len”──擦除的地址范围为start至(start+len
–1),“eraseall”──表示擦除所有NORFlash。
注意:其中的地址范围,刚好是一个扇区的开始地址到另一个(或同一个)扇区的结束地址。比如要擦除Amd29LV800BB的前5个扇区,执行的命令为“erase00x2ffff”,而非“erase00x30000”。
(5)NANDFlash操作命令。
NANDFlash操作命令只有一个:nand,它根据不同的参数进行不同操作,比如擦除、读取、烧写等。
“nandinfo”查看NANDFlash信息。
“nanderase[clean][offsize]”擦除NAND
Flash。加上“clean”时,表示在每个块的第一个扇区的OOB区加写入清除标记;off、size表示要擦除的开始偏移地址和长度,如果省略
off和size,表示要擦除整个NANDFlash。
“nandread[.jffs2]addroffsize”从NANDFlash偏移地址off处读出size个字节的数据,存放到开始地址为addr的内存中。是否加后缀“.jffs”的差别只是读操作时的ECC较验方法不同。
“nandwrite[.jffs2]addroffsize”把开始地址为addr的内存中的size个字节数据,写到NANDFlash的偏移地址off处。是否加后缀“.jffs”的差别只是写操作时的ECC较验方法不同。
“nandread.yaffsaddroffsize”从NANDFlash偏移地址off处读出size个字节的数据(包括OOB区域),存放到开始地址为addr的内存中。
“nandwrite.yaffsaddroffsize”把开始地址为addr的内存中的size个字节数据(其中有要写入OOB区域的数据),写到NANDFlash的偏移地址off处。
“nanddumpoff”,将NANDFlash偏移地址off的一个扇区的数据打印出来,包括OOB数据。
(6)环境变量命令。
“printenv”命令打印全部环境变量,“printenvname1name2...”打印名字为name1、name2、……”的环境变量。
“setenvnamevalue”设置名字为name的环境变量的值为value。
“setenvname”删除名字为name的环境变量。
上面的设置、删除操作只是在内存中进行,“saveenv”将更改后的所有环境变量写入NORFlash中。
(7)启动命令。
不带参数的“boot”、“bootm”命令都是执行环境变量bootcmd所指定的命令。
“bootm[addr[arg
...]]”命令启动存放在地址addr处的U-Boot格式的映像文件(使用U-Boot目录tools下的mkimage工具制作得到),[arg
...]表示参数。如果addr参数省略,映像文件所在地址为配置文件中定义的宏CFG_LOAD_ADDR。
“goaddr[arg...]”与bootm命令类似,启动存放在地址addr处的二进制文件,[arg...]表示参数。
“nboot[[[loadAddr]dev]offset]”命令将NAND
Flash设备dev上偏移地址off处的映像文件复制到内存loadAddr处,然后,如果环境变量autostart的值为“yes”,就启动这个映
像。如果loadAddr参数省略,存放地址为配置文件中定义的宏CFG_LOAD_ADDR;如果dev参数省略,则它的取值为环境变量
bootdevice的值;如果offset参数省略,则默认为0。
2.U-Boot命令使用实例
下面通过一个例子来演示如何使用各种命令烧写内核映像文件、yaffs映像文件,并启动系统。
(1)制作内核映像文件。
对于本书使用的Linux2.6.22.6版本,编译内核时可以直接生成U-Boot格式的映像文件uImage。
对于不能直接生成uImage的内核,制作方法在U-Boot根目录下的README文件中
有说明,假设已经编译好的内核文件为vmlinux,它是ELF格式的。mkimage是U-Boot目录tools下的工具,它在编译U-Boot时自
动生成。执行以下3个命令将内核文件vmlinux制作为U-Boot格式的映像文件uImage,它们首先将vmlinux转换为二进制格式,然后压
缩,最后构造头部信息(里面包含有文件名称、大小、类型、CRC较验码等):
①arm-linux-objcopy-Obinary-R.note-R.comment-Svmlinuxlinux.bin
②gzip-9linux.bin
③mkimage-Aarm-Olinux-Tkernel-Cgzip-a0x30008000-e0x30008000-n"LinuxKernelImage"-dlinux.bin.gzuImage
(2)烧写内核映像文件uImage。
首先将uImage放在主机上的tftp或nfs目录下,确保已经开启tftp或nfs服务。
然后运行如下命令下载文件,擦除、烧写NANDFlash:
①tftp0x30000000uImage或nfs0x30000000192.168.1.57:/work/nfs_root/uImage
②nanderase0x00x00200000
③nandwrite.jffs20x300000000x0$(filesize)
第3条命令之所以使用“nandwrite.jffs2”而不是“nand
write”,是因为前者不要求文件的长度是页对齐的(512字节对齐)。也可以使用“nand
write”,但是需要将命令中的长度参数改为$(filesize)向上进行512取整后的值。比如uImage的大小为1540883,向上进行
512取整后为1541120(即0x178400),可以使用命令“nandwrite0x300000000x0
0x178400”进行烧写。
(3)烧写yaffs文件系统映像。
假设yaffs文件系统映像的文件名为yaffs.img,首先将它放在主机上的tftp或nfs目录下,确保已经开启tftp或nfs服务;然后执行如下命令下载、擦除、烧写:
①tftp0x30000000yaffs.img或nfs0x30000000192.168.1.57:/work/nfs_root/yaffs.img
②nanderase0xA000000x3600000
③nandwrite.yaffs0x300000000xA00000$(filesize)
这时,重启系统,在U-Boot倒数3秒之后,就会自动启动Linux系统。
(4)烧写jffs2文件系统映像。
假设jffs2文件系统映像的文件名为jffs2.img,首先将它放在主机上的tftp或nfs目录下,确保已经开启tftp或nfs服务;然后执行如下命令下载、擦除、烧写:
①tftp0x30000000jffs2.img或nfs0x30000000192.168.1.57:/work/nfs_root/jffs2.img
②nanderase0x2000000x800000
③nandwrite.jffs20x300000000x200000$(filesize)
系统启动后,就可以使用“mount-tjffs2/dev/mtdblock1/mnt”挂接jffs2文件系统。
2.7使用U-Boot来执行程序
在前面的硬件实验中使用JTAG烧写程序到NAND
Flash,烧写过程十分缓慢。如果使用U-Boot来烧写NANDFlash,效率会高很多。烧写二进制文件到NAND
Flash中所使用的命令与上面烧写内核映像文件uImage的过程类似,只是不需要将二进制文件制作成U-Boot格式。
另外,可以将程序下载到内存中,然后使用go命令执行它。假设有一个程序的二进制可执行文件
test.bin,连接地址为0x30000000。首先将它放在主机上的tftp或nfs目录下,确保已经开启tftp或nfs服务;然后将它下载到内
存0x30000000处,最后使用go命令执行它:
①tftp0x30000000test.bin或nfs0x30000000192.168.1.57:/work/nfs_root/test.bin
②go0x30000000
一、Bootloader的引入
从前面的硬件实验可以知道,系统上电之后,需要一段程序来进行初始化:关闭
WATCHDOG、改变系统时钟、初始化存储控制器、将更多的代码复制到内存中等等。如果它能将操作系统内核(无论从本地,比如Flash;还是从远端,
比如通过网络)复制到内存中运行,就称这段程序为Bootloader。
简单地说,Bootloader就是这么一小段程序,它在系统上电时开始执行,初始化硬件设备、准备好软件环境,最后调用操作系统内核。
可以增强Bootloader的功能,比如增加网络功能、从PC上通过串口或网络下载文件、
烧写文件、将Flash上压缩的文件解压后再运行等──这就是一个功能更为强大的Bootloader,也称为Monitor。实际上,在最终产品中用户
并不需要这些功能,它们只是为了方便开发。
Bootloader的实现严重依赖于具体硬件,在嵌入式系统中硬件配置千差万别,即使是相
同的CPU,它的外设(比如Flash)也可能不同,所以不可能有一个Bootloader支持所有的CPU、所有的电路板。即使是支持CPU架构比较多
的U-Boot,也不是一拿来就可以使用的(除非里面的配置刚好与你的板子相同),需要进行一些移植。
二、Bootloader的启动方式
CPU上电后,会从某个地址开始执行。比如MIPS结构的CPU会从0xBFC00000取
第一条指令,而ARM结构的CPU则从地址0x0000000开始。嵌入式单板中,需要把存储器件ROM或Flash等映射到这个地
址,Bootloader就存放在这个地址开始处,这样一上电就可以执行。
在开发时,通常需要使用各种命令操作Bootloader,一般通过串口来连接PC和开发
板,可以在串口上输入各种命令、观察运行结果等。这也只是对开发人员才有意义,用户使用产品时是不用接串口来控制Bootloader的。从这个观点来
看,Bootloader可以分为两种操作模式(OperationMode):
(1)启动加载(Bootloading)模式。
上电后,Bootloader从板子上的某个固态存储设备上将操作系统加载到RAM中运行,整个过程并没有用户的介入。产品发布时,Bootloader工作在这种模式下。
(2)下载(Downloading)模式。
在这种模式下,开发人员可以使用各种命令,通过串口连接或网络连接等通信手段从主机(Host)下载文件(比如内核映像、文件系统映像),将它们直接放在内存运行或是烧入Flash类固态存储设备中。
板子与主机间传输文件时,可以使用串口的xmodem/ymodem/zmodem协议,它们使用简单,只是速度比较慢;还可以使用网络通过tftp、nfs协议来传输,这时,主机上要开启tftp、nfs服务;还有其他方法,比如USB等。
像Blob或U-Boot等这样功能强大的Bootloader通常同时支持这两种工作模
式,而且允许用户在这两种工作模式之间进行切换。比如,U-Boot在启动时处于正常的启动加载模式,但是它会延时若干秒(这可以设置)等待终端用户按下
任意键而将U-Boot切换到下载模式。如果在指定时间内没有用户按键,则U-Boot继续启动Linux内核。
编辑]15.1.2Bootloader的结构和启动过程
1.概述
在移植之前先了解Bootloader的一些通用概念,对理解它的代码会有所帮助。
在一个嵌入式Linux系统中,从软件的角度通常可以分为4个层次:
(1)引导加载程序,包括固化在固件(firmware)中的boot代码(可选)和Bootloader两大部分。
有些CPU在运行Bootloader之前先运行一段固化的程序(固件,firmware),比如x86结构的CPU就是先运行BIOS中的固件,然后才运行硬盘第一个分区(MBR)中的Bootloader。
在大多嵌入式系统中并没有固件,Bootloader是上电后执行的第一个程序。
(2)Linux内核。
特定于嵌入式板子的定制内核以及内核的启动参数。内核的启动参数可以是内核默认的,或是由Bootloader传递给它的。
(3)文件系统。
包括根文件系统和建立于Flash内存设备之上的文件系统。里面包含了Linux系统能够运行所必需的应用程序、库等,比如可以给用户提供操作Linux的控制界面的shell程序,动态连接的程序运行时需要的glibc或uClibc库,等等。
(4)用户应用程序。
特定于用户的应用程序,它们也存储在文件系统中。有时在用户应用程序和内核层之间可能还会包括一个嵌入式图形用户界面。常用的嵌入式GUI有:Qtopia和MiniGUI等。
显然,在嵌入系统的固态存储设备上有相应的分区来存储它们,图15.1是一个典型的分区结构。[[Image:]]
图15.1嵌入式Linux系统中的典型分区结构
“Boot
parameters”分区中存放一些可设置的参数,比如IP地址、串口波特率、要传递给内核的命令行参数等。正常启动过程中,Bootloader首先
运行,然后它将内核复制到内存中(也有些内核可以在固态存储设备上直接运行),并且在内存某个固定的地址设置好要传递给内核的参数,最后运行内核。内核启
动之后,它会挂接(mount)根文件系统(“Rootfilesystem”),启动文件系统中的应用程序。
2.Bootloader的两个阶段
Bootloader的启动过程启动过程可以分为单阶段(Single
Stage)、多阶段(Multi-Stage)两种。通常多阶段的Bootloader能提供更为复杂的功能,以及更好的可移植性。从固态存储设备上启
动的Bootloader大多都是2阶段的启动过程。这从前面的硬件实验可以很好地理解这点:第一阶段使用汇编来实现,它完成一些依赖于CPU
体系结构的初始化,并调用第二阶段的代码。第二阶段则通常使用C语言来实现,这样可以实现更复杂的功能,而且代码会有更好的可读性和可移植性。
一般而言,这两个阶段完成的功能可以如下分类,但这不是绝对的:
(1)Bootloader第一阶段的功能。
硬件设备初始化。
为加载Bootloader的第二阶段代码准备RAM空间。
拷贝Bootloader的第二阶段代码到RAM空间中。
设置好栈。
跳转到第二阶段代码的C入口点。
在第一阶段进行的硬件初始化一般包括:关闭WATCHDOG、关中断、设置CPU的速度和时钟频率、RAM初始化等。这些并不都是必需的,比如S3C2410/S3C2440的开发板所使用的U-Boot中,就将CPU的速度和时钟频率的设置放在第二阶段。
甚至,将第二阶段的代码复制到RAM空间中也不是必需的,对于NORFlash等存储设备,完全可以在上面直接执行代码,只不过这相比在RAM中执行效率大为降低。
(2)Bootloader第二阶段的功能。
初始化本阶段要使用到的硬件设备。
检测系统内存映射(memorymap)。
将内核映像和根文件系统映像从Flash上读到RAM空间中。
为内核设置启动参数。
调用内核。
为了方便开发,至少要初始化一个串口以便程序员与Bootloader进行交互。
所谓检测内存映射,就是确定板上使用了多少内存,它们的地址空间是什么。由于嵌入式开发中,Bootloader多是针对某类板子进行编写,所以可以根据板子的情况直接设置,不需要考虑可以适用于各类情况的复杂算法。
Flash上的内核映像有可能是经过压缩的,在读到RAM之后,还需要进行解压。当然,对于有自解压功能的内核,不需要Bootloader来解压。
将根文件系统映像复制到RAM中,这不是必需的。这取决于是什么类型的根文件系统,以及内核访问它的方法。
为内核设置启动参数将在下一小节介绍。
将内核存放在适当的位置后,直接跳到到它的入口点即可调用内核。调用内核之前,下列条件要满足:
(1)CPU寄存器的设置。
R0=0
R1=机器类型ID;对于ARM结构的CPU,其机器类型ID可以参见linux/arch/arm/tools/mach-types。
R2=启动参数标记列表在RAM中起始基地址
(2)CPU工作模式。
必须禁止中断(IRQs和FIQs)
CPU必须SVC模式
(3)Cache和MMU的设置。
MMU必须关闭
指令Cache可以打开也可以关闭
数据Cache必须关闭
如果用C语言,可以像下列示例代码一样来调用内核:
void(*theKernel)(intzero,intarch,u32
params_addr)=(void(*)(int,int,u32))KERNEL_RAM_BASE;……
theKernel(0,ARCH_NUMBER,(u32)kernel_params_start);
3.Bootloader与内核的交互
Bootloader与内核的交互是单向的,Bootloader将各类参数传给内核。由于它们不能同时运行,传递办法只有一个:Bootloader将参数放在某个约定的地方之后,再启动内核,内核启动后从这个地方获得参数。
除了约定好参数存放的地址外,还要规定参数的结构。Linux2.4.x
以后的内核都期望以标记列表(tagged
list)的形式来传递启动参数。标记,就是一种数据结构;标记列表,就是挨着存放的多个标记。标记列表以标记ATAG_CORE
开始,以标记ATAG_NONE
结束。标记的数据结构为tag,它由一个tag_header结构和一个联合(union)组成。tag_header结构表示标记的类型及长度,比如是
表示内存还是表示命令行参数等。对于不同类型的标记使用不同的联合(union),比如表示内存时使用tag_mem32,表示命令行时使用
tag_cmdline。数据结构tag和tag_header定义在Linux内核源码的include/asm/setup.h头文件中:
1 | struct tag_header{u32size;u32tag;}; |
2 | <br> struct tag{ struct tag_headerhdr; union { struct |
3 | tag_corecore; struct tag_mem32mem; struct tag_videotextvideotext; |
4 | struct tag_ramdiskramdisk; struct tag_initrdinitrd; struct |
5 | tag_serialnrserialnr; struct tag_revisionrevision; struct |
6 | tag_videolfbvideolfb; struct tag_cmdlinecmdline;<br> /**Acorn |
7 | specific*/ struct tag_acornacorn;<br> /**DC21285specific |
8 | */ struct tag_memclkmemclk;}u;}; |
(1)设置标记ATAG_CORE。
标记列表以标记ATAG_CORE开始,假设Bootloader与内核约定的参数存放地址为0x30000100,则可以以如下代码设置标记ATAG_CORE:
params=(structtag*)0x30000100;
<br>params->hdr.tag=ATAG_CORE;params->hdr.size=
tag_size(tag_core);<br>params->u.core.flags=0;
params->u.core.pagesize=0;params->u.core.rootdev=0;
<br>params=tag_next(params);
其中,tag_next定义如下,它指向当前标记的末尾:
#definetag_next(t)((structtag*)((u32*)(t)+(t)->hdr.size))
(2)设置内存标记。
假设开发板使用的内存起始地址为0x30000000,大小为0x4000000,则内存标记可以如下设置:
params->hdr.tag=ATAG_MEM;
params->hdr.size=tag_size(tag_mem32);
params->u.mem.start=0x30000000;
params->u.mem.size=0x4000000;
params=tag_next(params);
(3)设置命令行标记。
命令行就是一个字符串,它被用来控制内核的一些行为。比如"root=/dev
/mtdblock2init=/linuxrc
console=ttySAC0"表示根文件系统在MTD2分区上,系统启动后执行的第一个程序为/linuxrc,控制台为ttySAC0(即第一个串
口)。
命令行可以在Bootloader中通过命令设置好,然后如下构造标记传给内核:
char*p="root=/dev/mtdblock2init=/linuxrcconsole=ttySAC0";
params->hdr.tag=ATAG_CMDLINE;
params->hdr.size=(sizeof(structtag_header)+strlen(p)+1+4)>>2;
strcpy(params->u.cmdline.cmdline,p);
params=tag_next(params);
(4)设置标记ATAG_NONE。
标记列表以标记ATAG_NONE结束,如下设置:
params->hdr.tag=ATAG_NONE;
params->hdr.size=0;
常用Bootloader介绍
现在Bootloader种类繁多,比如x86上有LILO、GRUB等。对于ARM架构的CPU,有U-Boot、Vivi等。它们各有特点,下面列出Linux的开放源代码的Bootloader及其支持的体系架构,如表15.1所示。
开放源码的Linux引导程序
<tablefck__showtableborders?="">
Bootloader
Monitor
描述
X86
ARM
PowerPC
LILO
否
Linux磁盘引导程序
是
否
否
GRUB
否
GNU的LILO替代程序
是
否
否
Loadlin
否
从DOS引导Linux
是
否
否
ROLO
否
从ROM引导Linux而不需要BIOS
是
否
否
Etherboot
否
通过以太网卡启动Linux系统的固件
是
否
否
LinuxBIOS
否
完全替代BUIS的Linux引导程序
是
否
否
BLOB
是
LART等硬件平台的引导程序
否
是
否
U-Boot
是
通用引导程序
是
是
是
RedBoot
是
基于eCos的引导程序
是
是
是
Vivi
是
Mizi公司针对SAMSUNG的ARMCPU设计的引导程序
否
是
否
对于本书使用的S3C2410/S3C2440开发板,U-Boot和Vivi是两个好选
择。Vivi是Mizi公司针对SAMSUNG的ARM架构CPU专门设计的,基本上可以直接使用,命令简单方便。不过其初始版本只支持串口下载,速度较
慢。在网上出现了各种改进版本:支持网络功能、USB功能、烧写YAFFS文件系统映像等。U-Boot则支持大多CPU,可以烧写EXT2、JFFS2
文件系统映像,支持串口下载、网络下载,并提供了大量的命令。相对于Vivi,它的使用更复杂,但是可以用来更方便地调试程序。
2U-Boot分析与移植
2.1U-Boot工程简介
U-Boot,全称为UniversalBoot
Loader,即通用Bootloader,是遵循GPL条款的开放源代码项目。其前身是由德国DENX软件工程中心的Wolfgang
Denk基于8xxROM的源码创建的PPCBOOT工程。后来整理代码结构使得非常容易增加其他类型的开发板、其他架构的CPU(原来只支持
PowerPC);增加更多的功能,比如启动Linux、下载S-Record格式的文件、通过网络启动、通过PCMCIA/CompactFLash
/ATAdisk/SCSI等方式启动。增加ARM架构CPU及其他更多CPU的支持后,改名为U-Boot。
它的名字“通用”有两层含义:可以引导多种操作系统、支持多种架构的CPU。它支持如下操作
系统:Linux、NetBSD、
VxWorks、QNX、RTEMS、ARTOS、LynxOS等,支持如下架构的CPU:PowerPC、MIPS、x86、ARM、NIOS、
XScale等。
U-Boot有如下特性:
开放源码;
支持多种嵌入式操作系统内核,如Linux、NetBSD、VxWorks、QNX、RTEMS、ARTOS、LynxOS;
支持多个处理器系列,如PowerPC、ARM、x86、MIPS、XScale;
较高的可靠性和稳定性;
高度灵活的功能设置,适合U-Boot调试、操作系统不同引导要求、产品发布等;
丰富的设备驱动源码,如串口、以太网、SDRAM、FLASH、LCD、NVRAM、EEPROM、RTC、键盘等;
较为丰富的开发调试文档与强大的网络技术支持;
支持NFS挂载、RAMDISK(压缩或非压缩)形式的根文件系统
支持NFS挂载、从FLASH中引导压缩或非压缩系统内核;
可灵活设置、传递多个关键参数给操作系统,适合系统在不同开发阶段的调试要求与产品发布,尤对Linux支持最为强劲;
支持目标板环境变量多种存储方式,如FLASH、NVRAM、EEPROM;
CRC32校验,可校验FLASH中内核、RAMDISK镜像文件是否完好;
上电自检功能:SDRAM、FLASH大小自动检测;SDRAM故障检测;CPU型号;
特殊功能:XIP内核引导;
可以从
最新的更新代码地址
2.2U-Boot源码结构
本书在u-boot-1.1.6的基础上进行分析和移植,从sourceforge网站下载u-boot-1.1.6.tar.bz2后解压即得到全部源码。U-Boot源码目录结构比较简单、独立,目录结构也比较浅,很容易全部掌握。
u-boot-1.1.6根目录下共有26个子目录,可以分为4类:
(1)平台相关的或开发板相关的。
(2)通用的函数。
(3)通用的设备驱动程序。
(4)U-Boot工具、示例程序、文档。
先将这26个目录的功能与作用如表15.2所示。
表2U-Boot顶层目录说明
<tablefck__showtableborders?="">
目录
特性
解释说明
board
开发板相关
对应不同配置的电路板(即使CPU相同),比如smdk2410、sbc2410x
cpu
平台相关
对应不同的CPU,比如arm920t、arm925t、i386等;在它们的子目录下仍可以进一步细分,比如arm920t下就有at91rm9200、s3c24x0
lib_i386类似
某一架构下通用的文件
include
通用的函数
头文件和开发板配置文件,开发板的配置文件都放在include/configs目录下,U-Boot没有makemenuconfig类似的莱单来进行可视化配置,需要手动地修改配置文件中的宏定义
lib_generic
通用的库函数,比如printf等
common
通用的函数,多是对下一层驱动程序的进一步封装
disk
通用的设备驱动程序
硬盘接口程序
drivers
各类具体设备的驱动程序,基本上可以通用,它们通过宏从外面引入平台/开发板相关的函数
dtt
数字温度测量器或者传感器的驱动
fs
文件系统
nand_spl
U-Boot一般从ROM、NORFlash等设备启动,现在开始支持从NANDFlash启动,但是支持的CPU种类还不多
net
各种网络协议
post
上电自检程序
rtc
实时时钟的驱动
doc
文档
开发、使用文档
examples
示例程序
一些测试程序,可以使用U-Boot下载后运行
tools
工具
制作S-Record、U-Boot格式映像的工具,比如mkimage
U-Boot中各目录间也是有层次结构的,虽然这种分法不是绝对的,但是在移植过程中可以提供一些指导意义,如图2所示。
2U-Boot顶层目录的层次结构
比如common/cmd_nand.c文件提供了操作NAND
Flash的各种命令,这些命令通过调用drivers/nand/nand_base.c中的擦除、读写函数来实现。这些函数针对NAND
Flash的共性作了一些封装,将平台/开发板相关的代码用宏或外部函数来代替。而这些宏与外部函数,如果与平台相关,就要在下一层次的cpu
/xxx(xxx表示某型号的CPU)中实现;如果与开发板相关,就要在下一层次的board/xxx目录(xxx表示某款开发板)中实现。本书移植的
U-Boot,就是在cpu/arm920t/s3c24x0目录下增加了一个nand_flash.c文件来实现这些函数。
以增加烧写yaffs文件系统映像的功能为例──就是在common目录下的
cmd_nand.c中增加命令,比如nand
write.yaffs:这个命令要调用drivers/nand/nand_util.c中的相应函数,针对yaffs文件系统的特点依次调用擦除、烧
写函数。而这些函数依赖于drivers/nand/nand_base.c、cpu/arm920t/s3c24x0/nand_flash.c文件中
的相关函数。
目前u-boot-1.1.6支持10种架构──根目录下有10个类似lib_i386的目
录、31个型号(类型)的CPU──cpu目录下有31个子目录,214种开发板──board目录下有214个子目录,很容易从中找到与自己的板子相似
的配置,在上面稍作修改即可使用。
2.3U-Boot的配置、编译、连接过程
1.U-Boot初体验
u-boot-1.1.6中有几千个文件,要想了解对于某款开发板,使用哪些文件、哪个文件首先执行、可执行文件占用内存的情况,最好的方法就是阅读它的Makefile。
根据顶层Readme文件的说明,可以知道如果要使用开发板board/<board_name>,就先执行“make<board_name>_config”命令进行配置,然后执行“makeall”,就可以生成如下3个文件:
u-boot.bin:二进制可执行文件,它就是可以直接烧入ROM、NORFlash的文件。
u-boot:ELF格式的可执行文件
u-boot.srec:MotorolaS-Record格式的可执行文件
对于S3C2410的开发板,执行“makesmdk2410_config”、“makeall”后生成的u-boot.bin可以烧入NORFlash中运行。启动后可以看到串口输出一些信息后进入控制界面,等待用户的输入。
对于S3C2440的开发板,烧入上面生成的u-boot.bin,串口无输出,需要修改代码。
在修改代码之前,先看看上面两个命令“makesmdk2410_config”、“makeall”做了什么事情,以了解程序的流程,知道要修改哪些文件。
另外,编译U-Boot成功后,还会在它的tools子目录下生成一些工具,比如mkimage等。将它们复制到/usr/local/bin目录下,以后就可以直接使用它们了,比如编译内核时,会使用mkimage来生成U-Boot格式的内核映像文件uImage。
2.U-Boot的配置过程
在顶层Makefile中可以看到如下代码:
SRCTREE:=$(CURDIR)
……
MKCONFIG:=$(SRCTREE)/mkconfig
……
smdk2410_config:unconfig
@$(MKCONFIG)$(@:_config=)armarm920tsmdk2410NULLs3c24x0
假定我们在u-boot-1.1.6的根目录下编译,则其中的MKCONFIG就是根目录下
的mkconfig文件。$(@:_config=)的结果就是将“smdk2410_config”中的“_config”去掉,结果为
“smdk2410”。所以“makesmdk2410_config”实际上就是执行如下命令:
./mkconfigsmdk2410armarm920tsmdk2410NULLs3c24x0
再来看看mkconfig的作用,在mkconfig文件开头第6行给出了它的用法:
06#Parameters:TargetArchitectureCPUBoard[VENDOR][SOC]
这里解释一下概念,对于S3C2410、S3C2440,它们被称为SoC(System
onChip),上面除CPU外,还集成了包括UART、USB控制器、NAND
Flash控制器等等设备(称为片内外设)。S3C2410/S3C2440中的CPU为arm920t。
以下,分步骤分析mkconfig的作用:
(1)确定开发板名称BOARD_NAME。
01 | 11APPEND=no#Default:Create new configfile |
02 |
03 | 12BOARD_NAME= "" #Nametoprintinmakeoutput |
04 |
05 | 13 |
06 |
07 | 14 while [$#-gt0]; do |
08 |
09 | 15 case "$1" in |
10 |
11 | 16--)shift; break ;; |
12 |
13 | 17-a)shift;APPEND=yes;; |
14 |
15 | 18-n)shift;BOARD_NAME= "${1%%_config}" ;shift;; |
16 |
17 | 19*) break ;; |
18 |
19 | 20esac |
20 |
21 | 21done |
22 |
23 | 22 |
24 |
25 | 23[ "${BOARD_NAME}" ]||BOARD_NAME= "$1" |
执行完第23行后,BOARD_NAME的值等于第1个参数,即“smdk2410”。
(2)创建到平台/开发板相关的头文件的链接。
略过mkconfig文件中的一些没有起作用的行:
01 | 30# |
02 |
03 | 31#Createlinktoarchitecturespecificheaders |
04 |
05 | 32# |
06 |
07 | 33 if [ "$SRCTREE" != "$OBJTREE" ];then |
08 |
09 | …… |
10 |
11 | 45 else |
12 |
13 | 46cd./include |
14 |
15 | 47rm-fasm |
16 |
17 | 48ln-sasm-$2asm |
18 |
19 | 49fi |
20 |
21 | 50 |
第46~48行进入include目录,删除asm文件(这是上一次配置时建立的链接文件),然后再次建立asm文件,并令它链接向asm-$2目录,即asm-arm。
继续往下看代码:
01 | 51rm-fasm-$2/arch |
02 |
03 | 52 |
04 |
05 | 53 if [-z "$6" -o "$6" = "NULL" ];then |
06 |
07 | 54ln-s${LNPREFIX}arch-$3asm-$2/arch |
08 |
09 | 55 else |
10 |
11 | 56ln-s${LNPREFIX}arch-$6asm-$2/arch |
12 |
13 | 57fi |
14 |
15 | 58 |
16 |
17 | 59 if [ "$2" = "arm" ];then |
18 |
19 | 60rm-fasm-$2/proc |
20 |
21 | 61ln-s${LNPREFIX}proc-armvasm-$2/proc |
22 |
23 | 62fi |
24 |
25 | 63 |
对于“./mkconfigsmdk2410armarm920tsmdk2410NULLs3c24x0”命令,$6为“s3c24x0”,不为空,也不是“NULL”,所以第53行的条件不满足,将执行else分支。
第56行中,LNPREFIX为空,所以这个命令实际上就是:ln-sarch-$6asm-$2/arch,即:ln-sarch-s3c24x0asm-arm/arch。
第60、61行重新建立asm-arm/proc文件,并让它链接向proc-armv目录。
(3)创建顶层Makefile包含的文件include/config.mk。
对于“./mkconfigsmdk2410armarm920tsmdk2410NULLs3c24x0”命令,上面几行代码创建的config.mk文件内容如下:
01 | 64# |
02 |
03 | 65#Createincludefile for Make |
04 |
05 | 66# |
06 |
07 | 67echo "ARCH=$2" >config.mk |
08 |
09 | 68echo "CPU=$3" >>config.mk |
10 |
11 | 69echo "BOARD=$4" >>config.mk |
12 |
13 | 70 |
14 |
15 | 71[ "$5" ]&&[ "$5" != "NULL" ]&&echo "VENDOR=$5" >>config.mk |
16 |
17 | 72 |
18 |
19 | 73[ "$6" ]&&[ "$6" != "NULL" ]&&echo "SOC=$6" >>config.mk |
20 |
21 | 74 |
CPU=arm920t
BOARD=smdk2410
SOC=s3c24x0
(4)创建开发板相关的头文件include/config.h。
01 | 75# |
02 |
03 | 76#Createboardspecificheaderfile |
04 |
05 | 77# |
06 |
07 | 78 if [ "$APPEND" = "yes" ]#Appendtoexistingconfigfile |
08 |
09 | 79then |
10 |
11 | 80echo>>config.h |
12 |
13 | 81 else |
14 |
15 | 82>config.h#Create new configfile |
16 |
17 | 83fi |
18 |
19 | 84echo "/*Automaticallygenerated-donotedit*/" >>config.h |
20 |
21 | 85echo "#include<configs/$1.h>" >>config.h |
22 |
23 | 86 |
/*Automaticallygenerated-donotedit*/
#include<configs/smdk2410.h>"
现在总结一下,配置命令“make
smdk2410_config”,实际的作用就是执行“./mkconfigsmdk2410armarm920tsmdk2410
NULLs3c24x0”命令。假设执行“./mkconfig$1$2$3$4$5$6”命令,则将产生如下结果:
(1)开发板名称BOARD_NAME等于$1;
(2)创建到平台/开发板相关的头文件的链接:
ln-sasm-$2asm
ln-sarch-$6asm-$2/arch
ln-sproc-armvasm-$2/proc#如果$2不是arm的话,此行没有
(3)创建顶层Makefile包含的文件include/config.mk。
ARCH=$2
CPU=$3
BOARD=$4
VENDOR=$5#$5为空,或者是NULL的话,此行没有
SOC=$6#$6为空,或者是NULL的话,此行没有
(4)创建开发板相关的头文件include/config.h。
/*Automaticallygenerated-donotedit*/
#include<configs/$1.h>"
从这4个结果可以知道,如果要在board目录下新建一个开发
板<board_name>的目录,则在include/config目录下也要建立一个文件<board_name>.h,里
面存放的就是开发板<board_name>的配置信息。
U-Boot还没有类似Linux一样的可视化配置界面(比如使用makemenuconfig来配置),要手动修改配置文件include/config/<board_name>.h来裁减、设置U-Boot。
配置文件中有两类宏:
(1)一类是选项(Options),前缀为“CONFIG_”,它们用于选择CPU、SOC、开发板类型,设置系统时钟、选择设备驱动等。比如:
#defineCONFIG_ARM920T1/*ThisisanARM920TCore*/
#defineCONFIG_S3C24101/*inaSAMSUNGS3C2410SoC*/
#defineCONFIG_SMDK24101/*onaSAMSUNGSMDK2410Board*/
#defineCONFIG_SYS_CLK_FREQ12000000/*theSMDK2410has12MHzinputclock*/
#defineCONFIG_DRIVER_CS89001/*wehaveaCS8900on-board*/
(2)另一类是参数(Setting),前缀为“CFG_”,它们用于设置malloc缓冲池的大小、U-Boot的提示符、U-Boot下载文件时的默认加载地址、Flash的起始地址等。比如:
#defineCFG_MALLOC_LEN(CFG_ENV_SIZE+128*1024)
#defineCFG_PROMPT"100ASK>"/*MonitorCommandPrompt*/
#defineCFG_LOAD_ADDR0x33000000/*defaultloadaddress*/
#definePHYS_FLASH_10x00000000/*FlashBank#1*/
从下面的编译、连接过程可知,U-Boot中几乎每个文件都被编译和连接,但是这些文件是否包含有效的代码,则由宏开关来设置。比如对于网卡驱动drivers/cs8900.c,它的格式为:
#include<common.h>/*将包含配置文件include/config/<board_name>.h*/
……
#ifdefCONFIG_DRIVER_CS8900
/*实际的代码*/
……
#endif/*CONFIG_DRIVER_CS8900*/
如果定义了宏CONFIG_DRIVER_CS8900,则文件中包含有效的代码;否则,文件被注释为空。
可以这样粗糙地认为,“CONFIG_”除了设置一些参数外,主要用来设置U-Boot的功能、选择使用文件中的哪一部分;而“CFG_”用来设置更细节的参数。
3.U-Boot的编译、连接过程
配置完后,执行“makeall”即可编译,从Makefile中可以了解U-Boot使用了哪些文件、哪个文件首先执行、可执行文件占用内存的情况。
先确定用到哪些文件,下面只摘取Makefile中与arm相关的部分:
01 | 117include$(OBJTREE)/include/config.mk |
02 |
03 | 118exportARCHCPUBOARDVENDORSOC |
04 |
05 | 119 |
06 |
07 | …… |
08 |
09 | 127ifeq($(ARCH),arm) |
10 |
11 | 128CROSS_COMPILE=arm-linux- |
12 |
13 | 129endif |
14 |
15 | …… |
16 |
17 | 163#loadotherconfiguration |
18 |
19 | 164include$(TOPDIR)/config.mk |
20 |
21 | 165 |
上面的配置过程中制作出来的include/config.mk文件,其中定义了ARCH、CPU、BOARD、SOC等4个变量的值为arm、
arm920t、smdk2410、s3c24x0。
第164行包含顶层目录的config.mk文件,它根据上面4个变量的值确定了编译器、编译选项等。其中对我们理解编译过程有帮助的是BOARDDIR、LDFLAGS的值,config.mk中:
01 | 88BOARDDIR=$(BOARD) |
02 |
03 | …… |
04 |
05 | 91sinclude$(TOPDIR)/board/$(BOARDDIR)/config.mk#includeboardspecificrules |
06 |
07 | …… |
08 |
09 | 143LDSCRIPT:=$(TOPDIR)/board/$(BOARDDIR)/u-boot.lds |
10 |
11 | …… |
12 |
13 | 189LDFLAGS+=-Bstatic-T$(LDSCRIPT)-Ttext$(TEXT_BASE)$(PLATFORM_LDFLAGS) |
0x33F80000”。所以,最终结果如下:BOARDDIR为smdk2410;LDFLAGS中有“-T
board/smdk2410/u-boot.lds-Ttext0x33F80000”字样。
继续往下看Makefile:
166#########################################################################
167#U-Bootobjects....orderisimportant(i.e.startmustbefirst)
168
169OBJS=cpu/$(CPU)/start.o
……
193LIBS=lib_generic/libgeneric.a
194LIBS+=board/$(BOARDDIR)/lib$(BOARD).a
195LIBS+=cpu/$(CPU)/lib$(CPU).a
……
199LIBS+=lib_$(ARCH)/lib$(ARCH).a
200LIBS+=fs/cramfs/libcramfs.afs/fat/libfat.afs/fdos/libfdos.afs/jffs2/libjffs2.a\
201fs/reiserfs/libreiserfs.afs/ext2/libext2fs.a
202LIBS+=net/libnet.a
……
212LIBS+=$(BOARDLIBS)
213
……
从第169行得知,OBJS的第一个值为“cpu/$(CPU)/start.o”,即“cpu/arm920t/start.o”。
第193~213行指定了LIBS变量就是平台/开发板相关的各个目录、通用目录下相应的
库,比如:lib_generic/libgeneric.a、board/smdk2410/libsmdk2410.a、cpu/arm920t
/libarm920t.a、lib_arm/libarm.a、fs/cramfs/libcramfs.afs/fat/libfat.a等。
OBJS、LIBS所代表的.o、.a文件就是U-Boot的构成,它们通过如下命令由相应的源文件(或相应子目录下的文件)编译得到。
268$(OBJS):
269$(MAKE)-Ccpu/$(CPU)$(if$(REMOTE_BUILD),$@,$(notdir$@))
270
271$(LIBS):
272$(MAKE)-C$(dir$(subst$(obj),,$@))
273
274$(SUBDIRS):
275$(MAKE)-C$@all
276
第268、269两行的规则表示,对于OBJS中的每个成员,都将进入cpu/$(CPU)目录(即cpu/arm920t)编译它们。现在OBJS为cpu/arm920t/start.o,它将由cpu/arm920t/start.S编译得到。
第271、272两行的规则表示,对于LIBS中的每个成员,都将进入相应的子目录执行“make”命令。这些子目录中的Makefile,结构相似,它们将Makefle中指定的文件编译、连接成一个库文件。
当所有的OBJS、LIBS所表示的.o和.a文件都生成后,就剩最后的连接了,这对应Makefile中如下几行:
246$(obj)u-boot.srec:$(obj)u-boot
247$(OBJCOPY)${OBJCFLAGS}-Osrec$<$@
248
249$(obj)u-boot.bin:$(obj)u-boot
250$(OBJCOPY)${OBJCFLAGS}-Obinary$<$@
251
……
262$(obj)u-boot:dependversion$(SUBDIRS)$(OBJS)$(LIBS)$(LDSCRIPT)
263UNDEF_SYM=`$(OBJDUMP)-x$(LIBS)|sed-n-e's/.*\(__u_boot_cmd_.*\)/-u\1/p'|sort|uniq`;\
264cd$(LNDIR)&&$(LD)$(LDFLAGS)$$UNDEF_SYM$(__OBJS)\
265--start-group$(__LIBS)--end-group$(PLATFORM_LIBS)\
266-Mapu-boot.map-ou-boot
267
先使用第262~266的规则连接得到ELF格式的u-boot,最后转换为二进制格式u-
boot.bin、S-Record格式u-boot.srec。LDFLAGS确定了连接方式,其中的“-T
board/smdk2410/u-boot.lds-Ttext
0x33F80000”字样指定了程序的布局、地址。board/smdk2410/u-boot.lds文件如下:
28SECTIONS
29{
30.=0x00000000;
31
32.=ALIGN(4);
33.text:
34{
35cpu/arm920t/start.o(.text)
36*(.text)
37}
38
39.=ALIGN(4);
40.rodata:{*(.rodata)}
41
42.=ALIGN(4);
43.data:{*(.data)}
44
45.=ALIGN(4);
46.got:{*(.got)}
47
48.=.;
49__u_boot_cmd_start=.;
50.u_boot_cmd:{*(.u_boot_cmd)}
51__u_boot_cmd_end=.;
52
53.=ALIGN(4);
54__bss_start=.;
55.bss:{*(.bss)}
56_end=.;
57}
从第35行可知,cpu/arm920t/start.o被放在程序的最前面,所以U-Boot的入口点在cpu/arm920t/start.S中。
现在来总结一下U-Boot的编译流程:
(1)首先编译cpu/$(CPU)/start.S,对于不同的CPU,还可能编译cpu/$(CPU)下的其他文件。
(2)然后,对于平台/开发板相关的每个目录、每个通用目录,都使用它们各自的Makefile生成相应的库。
(3)将1、2步骤生成的.o、.a文件按照board/$(BOARDDIR)/config.mk文件中指定的代码段起始地址、board/$(BOARDDIR)/u-boot.lds连接脚本进行连接。
(4)第3步得到的是ELF格式的U-Boot,后面Makefile还会将它转换为二进制格式、S-Record格式。
2.4U-Boot的启动过程源码分析
首先强调,本书使用的U-Boot从NORFlash启动,下面以开发板smdk2410的U-Boot为例。
U-Boot属于两阶段的Bootloader,第一阶段的文件为cpu/arm920t/start.S和board/smdk2410/lowlevel_init.S,前者是平台相关,后者是开发板相关。
U-Boot第一阶段代码分析
它与1.2节中描述的Bootloader第一阶段所完成的功能可以一一对应:
(1)硬件设备初始化。
依次完成如下设置:将CPU的工作模式设为管理模式(svc),关闭WATCHDOG,设置FCLK、HCLK、PCLK的比例(即设置CLKDIVN寄存器),关闭MMU、CACHE。
代码都在cpu/arm920t/start.S中,注释也比较完善,读者有不明白的地方可以参考前面硬件实验的相关章节。
(2)为加载Bootloader的第二阶段代码准备RAM空间。
所谓准备RAM空间,就是初始化内存芯片,使它可用。对于S3C2410/S3C2440,
通过在start.S中调用lowlevel_init函数来设置存储控制器,使得外接的SDRAM可用。代码在board/smdk2410
/lowlevel_init.S中。
注意:lowlevel_init.S文件是开发板相关的,这表示如果外接的设备不一样,可以修改lowlevel_init.S文件中的相关宏。
lowlevel_init函数并不复杂,只是要注意这时的代码、数据都只保存在NORFlash上,内存中还没有,所以读取数据时要变换地址。代码如下:
129_TEXT_BASE:
130.wordTEXT_BASE
131
132.globllowlevel_init
133lowlevel_init:
134/*memorycontrolconfiguration*/
135/*maker0relativethecurrentlocationsothatit*/
136/*readsSMRDATAoutofFLASHratherthanmemory!*/
137ldrr0,=SMRDATA
138ldrr1,_TEXT_BASE
139subr0,r0,r1
140ldrr1,=BWSCON/*BusWidthStatusController*/
141addr2,r0,#13*4
1420:
143ldrr3,[r0],#4
144strr3,[r1],#4
145cmpr2,r0
146bne0b
147
148/*everythingisfinenow*/
149movpc,lr
150
151.ltorg
152/*theliteralpoolsorigin*/
153
154SMRDATA:/*13个寄存器的值*/
155.word……
156.word……
第137~139行进行地址变换,因为这时候内存中还没有数据,不能使用连接程序时确定的地址来读取数据:
第137行中SMRDATA表示这13个寄存器的值存放的开始地址(连接地址),值为0x33F8xxxx,处于内存中。
第138行获得代码段的起始地址,它就是第130行中的“TEXT_BASE”,其值在board/smdk2410/config.mk中定义:“TEXT_BASE=0x33F80000”。
第139行将0x33F8xxxx与0x33F80000相减,这就是13个寄存器值在NORFlash上存放的开始地址。
(3)拷贝Bootloader的第二阶段代码到RAM空间中。
这里将整个U-Boot的代码(包括第一、第二阶段)都复制到SDRAM中,这在cpu/arm920t/start.S中实现:
164relocate:/*将U-Boot复制到RAM中*/
165adrr0,_start/*r0=当前代码的开始地址*/
166ldrr1,_TEXT_BASE/*r1=代码段的连接地址*/
167cmpr0,r1/*测试现在是在Flash中还是在RAM中*/
168beqstack_setup/*如果已经在RAM中(这通常是调试时,直接下载到RAM中),
*则不需要复制
*/
169
170ldrr2,_armboot_start/*_armboot_start在前面定义,是第一条指令的运行地址*/
171ldrr3,_bss_start/*在连接脚本u-boot.lds中定义,是代码段的结束地址*/
172subr2,r3,r2/*r2=代码段长度*/
173addr2,r0,r2/*r2=NORFlash上代码段的结束地址*/
174
175copy_loop:
176ldmiar0!,{r3-r10}/*从地址[r0]处获得数据*/
177stmiar1!,{r3-r10}/*复制到地址[r1]处*/
178cmpr0,r2/*判断是否复制完毕*/
179blecopy_loop/*没复制完,则继续*/
(4)设置好栈。
栈的设置灵活性很大,只要让sp寄存器指向一段没有使用的内存即可。
182/*Setupthestack*/
183stack_setup:
184ldrr0,_TEXT_BASE/*_TEXT_BASE为代码段的开始地址,值为0x33F80000*/
185subr0,r0,#CFG_MALLOC_LEN/*代码段下面,留出一段内存以实现malloc*/
186subr0,r0,#CFG_GBL_DATA_SIZE/*再留出一段内存,存一些全局参数*/
187#ifdefCONFIG_USE_IRQ
188subr0,r0,#(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)/*IRQ、FIQ模式的栈*/
189#endif
190subsp,r0,#12/*最后,留出12字节的内存给abort异常,
*往下的内存就都是栈了
*/
191
到了这一步,读者可以知道内存的使用情况了,如下图所示(图中与上面的划分稍有不同,这是因为在cpu/arm920t/cpu.c中的cpu_init函数中才真正为IRQ、FIQ模式划分了栈):[[Image:]]
图3U-Boot内存使用情况
(5)跳转到第二阶段代码的C入口点。
在跳转之前,还要清除BSS段(初始值为0、无初始值的全局变量、静态变量放在BSS段),代码如下:
192clear_bss:
193ldrr0,_bss_start/*BSS段的开始地址,它的值在连接脚本u-boot.lds中确定*/
194ldrr1,_bss_end/*BSS段的结束地址,它的值在连接脚本u-boot.lds中确定*/
195movr2,#0x00000000
196
197clbss_l:strr2,[r0]/*往BSS段中写入0值*/
198addr0,r0,#4
199cmpr0,r1
200bleclbss_l
201
现在,C函数的运行环境已经完全准备好,通过如下命令直接跳转(这之后,程序才在内存中执行),它将调用lib_arm/board.c中的start_armboot函数,这是第二阶段的入口点:
223ldrpc,_start_armboot
224
225_start_armboot:.wordstart_armboot
226
U-Boot第二阶段代码分析
它与15.1.2节中描述的Bootloader第二阶段所完成的功能基本上一致,不过顺序有点小差别。另外,U-Boot在启动内核之前可以让用户决定是否进入下载模式,即进入U-Boot的控制界面。
第二阶段从lib_arm/board.c中的start_armboot函数开始,先看从这个函数开始的程序流程图。
图3U-Boot第二阶段流程图
移植U-Boot的主要工作在于对硬件的初始化、驱动,所以下面讲解时将重点放在硬件的操作上。
(1)初始化本阶段要使用到的硬件设备。:最主要的是设置系统时钟、初始化串口,只要这两个设置好了,就可以从串口看到打印信息。
board_init函数设置MPLL、改变系统时钟,它是开发板相关的函数,在board/smdk2410/smdk2410.c中实现。值得注意的是,board_init函数中还保存了机器类型ID,这将在调用内核时传给内核,代码如下:
/*archnumberofSMDK2410-Board*/
gd->bd->bi_arch_number=MACH_TYPE_SMDK2410;/*值为193*/
串口的初始化函数主要是serial_init,它设置UART控制器,是CPU相关的函数,在cpu/arm920t/s3c24x0/serial.c中实现。
(2)检测系统内存映射(memory
map)。对于特定的开发板,其内存的分布是明确的,所以可以直接设置。board/smdk2410/smdk2410.c中的dram_init函数
指定了本开发板的内存起始地址为0x30000000,大小为0x4000000。代码如下:
intdram_init(void)
{
gd->bd->bi_dram[0].start=PHYS_SDRAM_1;/*即0x300000000*/
gd->bd->bi_dram[0].size=PHYS_SDRAM_1_SIZE;/*即0x4000000*/
return0;
}
这些设置的参数,将在后面向内核传递参数时用到。
(3)U-Boot命令的格式。
从图3可以知道,即使是内核的启动,也是通过U-Boot命令来实现的。U-Boot中每个命令都通过U_BOOT_CMD宏来定义,格式如下:
U_BOOT_CMD(name,maxargs,repeatable,command,"usage","help")
各项参数的意义为:
①name:命令的名字,注意,它不是一个字符串(不要用双引号括起来)。
②maxargs:最大的参数个数
③repeatable:命令是否可重复,可重复是指运行一个命令后,下次敲回车即可再次运行。
④command:对应的函数指针,类型为(*cmd)(structcmd_tbl_s*,int,int,char*[])。
⑤usage:简短的使用说明,这是个字符串。
⑥help:较详细的使用说明,这是个字符串。
宏U_BOOT_CMD在include/command.h中定义:
#defineU_BOOT_CMD(name,maxargs,rep,cmd,usage,help)\
cmd_tbl_t__u_boot_cmd_##nameStruct_Section={#name,maxargs,rep,cmd,usage,help}
Struct_Section也是在include/command.h中定义:
#defineStruct_Section__attribute__((unused,section(".u_boot_cmd")))
比如对于bootm命令,它如此定义:
U_BOOT_CMD(
bootm,CFG_MAXARGS,1,do_bootm,
“string1”,
“string2”
);
宏U_BOOT_CMD扩展开后就是:
cmd_tbl_t__u_boot_cmd_bootm__attribute__
((unused,section(".u_boot_cmd")))={“bootm”,CFG_MAXARGS,1,
do_bootm,“string1”,“string2”};
对于每个使用U_BOOT_CMD宏来定义的命令,其实都是在".u_boot_cmd"段中定义一个cmd_tbl_t结构。连接脚本u-boot.lds中有这么一段:
__u_boot_cmd_start=.;
.u_boot_cmd:{*(.u_boot_cmd)}
__u_boot_cmd_end=.;
程序中就是根据命令的名字在内存段__u_boot_cmd_start~__u_boot_cmd_end找到它的cmd_tbl_t结构,然后调用它的函数(请参考common/command.c中的find_cmd函数)。
内核的复制和启动,可以通过如下命令来完成:bootm从内存、ROM、NOR
Flash中启动内核,bootp则通过网络来启动,而nboot从NAND
Flash启动内核。它们都是先将内核映像从各种媒介中读出,存放在指定的位置;然后设置标记列表以给内核传递参数;最后跳到内核的入口点去执行。具体实
现的细节不再描述,有兴趣的读者可以阅读common/cmd_boot.c、common/cmd_net.c、common/cmd_nand.c来
了解它们的实现。
(4)为内核设置启动参数。
与15.1.2小节中《Bootloader与内核的交互》所描述的一样,U-Boot也是
通过标记列表向内核传递参数。并且,15.1.2小节中内存标记、命令行标记的示例代码就是取自U-Boot中的setup_memory_tags、
setup_commandline_tag函数,它们都是在lib_arm/armlinux.c中定义。一般而言,设置这两个标记就可以了,在配置文
件include/configs/smdk2410.h中增加如下两个配置项即可:
#defineCONFIG_SETUP_MEMORY_TAGS1
#defineCONFIG_CMDLINE_TAG1
对于ARM架构的CPU,都是通过lib_arm/armlinux.c中的
do_bootm_linux函数来启动内核。这个函数中,设置标记列表,最后通过“theKernel(0,
bd->bi_arch_number,
bd->bi_boot_params)”调用内核。其中,theKernel指向内核存放的地址(对于ARM架构的CPU,通常是
0x30008000),bd->bi_arch_number就是前面board_init函数设置的机器类型ID,而
bd->bi_boot_params就是标记列表的开始地址。
2.5U-Boot的移植
开发板smdk2410的配置适用于大多数S3C2410单板,或是只需要极少的修改即可使用。但是目前U-Boot中没有对S3C2440的支持,需要我们自己移植。
本书基于的S3C2410、S3C2440两款开发板,它们的外接硬件相同:
BANK0外接容量为1MB,位宽为8的NORFlash芯片AM29LV800
BANK3外接10M网卡芯片CS8900,位宽为16
BANK6外接两片容量为32MB、位宽为16的SDRAM芯片K4S561632,组成容量为64MB、位宽为32的内存
通过NANDFlash控制器外接容量为64MB,位宽为8的NANDFlash芯片K9S1208
对于NORFlash和NAND
Flash,如图15.4所示划分它们的使用区域。由于NAND
Flash的“位反转”现象比较常见,为保证数据的正确,在读写数据时需要使用ECC较验。另外,NAND
Flash在使用过程中、运输过程中还有可能出现坏块。所以本书选择在NORFlash中保存U-Boot,在NAND
Flash中保存内核和文件系统,并在使用U-Boot烧写内核、文件系统时,进行坏块检查、ECC较验。这样,即使NAND
Flash出现坏块导致内核或文件系统不能使用,也可以通过NORFlash中的U-Boot来重新烧写。[[Image:]]
图15.4开发板固态存储器分区划分
smdk2410开发板已经支持NOR
Flash芯片AM29LV800,U-Boot本身也已经支持jffs2文件系统映像的烧写。下面一步一步移植U-Boot(所有的修改都在补丁文件
u-boot-1.1.6_100ask24x0.patch里,读者可以直接打补丁),增加如下新功能:
同时支持本书使用的S3C2410和S3C2440开发板
支持串口xmodem协议
支持网卡芯片CS8900
支持NANDFlash读写
支持烧写yaffs文件系统映像
1.同时支持S3C2410和S3C2440
我们将在开发板smdk2410的基础上进行移植。
(1)新建一个开发板的相应目录和文件。
为了不破坏原来的代码,在board目录下将smdk2410复制为100ask24x0目录,并将board/100ask24x0/smdk2410.c改名为100ask24x0.c。
根据前面描述的配置过程可知,还要在include/configs目录下建立一个配置文件100ask24x0.h,可以将include/configs/smdk2410.h直接复制为100ask24x0.h。
还要修改两个Makefile,首先在顶层Makefile中增加如下两行:
100ask24x0_config:unconfig
@$(MKCONFIG)$(@:_config=)armarm920t100ask24x0NULLs3c24x0
然后在board/100ask24x0/Makefile中,如下修改(因为前面将smdk2410.c文件改名为100ask24x0.c了):
COBJS:=smdk2410.oflash.o
改为:
COBJS:=100ask24x0.oflash.o
(2)修改SDRAM的配置。
SDRAM的初始化在U-Boot的第一阶段完成,就是在board/100ask24x0/lowlevel_init.S文件中设置存储控制器。
检查一下BANK6的设置:位宽为32──宏B6_BWSCON刚好为DW32(表示32位),无需改变;另外还要根据HCLK设置SDRAM的刷新参数,主要是REFCNT寄存器。
本书所用开发板的HCLK都设为100MHz,需要根据SDRAM芯片的具体参数重新计算REFCNT寄存器的值(请参考第6章)。代码修改如下:
126#defineREFCNT1113/*period=15.6us,HCLK=60Mhz,(2048+1-15.6*60)*/
改为
126#defineREFCNT0x4f4/*period=7.8125us,HCLK=100Mhz,(2048+1-7.8125*100)*/
对于其他BANK,比如网卡芯片CS8900所在的BANK2,原来的设置刚好匹配,无需更改;而对于BANK1、2、4、5、7,在U-Boot中并没有使用到它们外接的设备,也不需要理会。
(3)增加对S3C2440的支持。
S3C2440是S3C2410的改进版,它们的操作基本相似。不过在系统时钟的设置、
NAND
Flash控制器的操作等方面,有一些小差别。它们的MPLL、UPLL计算公式不一样,FCLK、HCLK和PCLK的分频化设置也不一样,这在下面的
代码中可以看到。NANDFlash控制器的差别在增加对NANDFlash的支持时讲述。
本章的目标是令同一个U-Boot二进制代码既可以在S3C2410上运行,也可以在
S3C2440上运行。首先需要在代码中自动识别是S3C2410还是S3C2440,这可以通过读取GSTATUS1寄存器的值来分
辨:0x32410000表示S3C2410,0x32410002表示S3C2410A,0x32440000表示
S3C2440,0x32440001表示S3C2440A。S3C2410和S3C2410A、S3C2440和S3C2440A,对本书来说没有区
别。
对于S3C2410开发板,将FCLK设为200MHz,分频比为
FCLK:HCLK:PCLK=1:2:4;对于S3C2440开发板,将FCLK设为400MHz,分频比为
FCLK:HCLK:PCLK=1:4:8。还将UPLL设为48MHz,即UCLK为48MHz,以在内核中支持USB控制器。
首先修改board/100ask24x0/100ask24x0.c中的board_init函数,下面是修改后的代码:
33/*S3C2440:MPLL=(2*m*Fin)/(p*2^s),UPLL=(m*Fin)/(p*2^s)
34*m=M(thevaluefordividerM)+8,p=P(thevaluefordividerP)+2
35*/
36#defineS3C2440_MPLL_400MHZ((0x5c<<12)|(0x01<<4)|(0x01))
37#defineS3C2440_UPLL_48MHZ((0x38<<12)|(0x02<<4)|(0x02))
38#defineS3C2440_CLKDIV0x05/*FCLK:HCLK:PCLK=1:4:8,UCLK=UPLL*/
39
40/*S3C2410:Mpll,Upll=(m*Fin)/(p*2^s)
41*m=M(thevaluefordividerM)+8,p=P(thevaluefordividerP)+2
42*/
43#defineS3C2410_MPLL_200MHZ((0x5c<<12)|(0x04<<4)|(0x00))
44#defineS3C2410_UPLL_48MHZ((0x28<<12)|(0x01<<4)|(0x02))
45#defineS3C2410_CLKDIV0x03/*FCLK:HCLK:PCLK=1:2:4*/
46
上面几行针对S3C2410、S3C2440分别定义了MPLL、UPLL寄存器的值。开发
板输入时钟为12MHz(这在include/configs/100ask24x0.h中的宏CONFIG_SYS_CLK_FREQ中定义),读者可
以根据代码中的计算公式针对自己的开发板修改系统时钟。
下面是针对S3C2410、S3C2440,分别使用不同的宏设置系统时钟:
58intboard_init(void)
59{
60S3C24X0_CLOCK_POWER*constclk_power=S3C24X0_GetBase_CLOCK_POWER();
61S3C24X0_GPIO*constgpio=S3C24X0_GetBase_GPIO();
62
63/*设置GPIO*/
64gpio->GPACON=0x007FFFFF;
65gpio->GPBCON=0x00044555;
66gpio->GPBUP=0x000007FF;
67gpio->GPCCON=0xAAAAAAAA;
68gpio->GPCUP=0x0000FFFF;
69gpio->GPDCON=0xAAAAAAAA;
70gpio->GPDUP=0x0000FFFF;
71gpio->GPECON=0xAAAAAAAA;
72gpio->GPEUP=0x0000FFFF;
73gpio->GPFCON=0x000055AA;
74gpio->GPFUP=0x000000FF;
75gpio->GPGCON=0xFF95FFBA;
76gpio->GPGUP=0x0000FFFF;
77gpio->GPHCON=0x002AFAAA;
78gpio->GPHUP=0x000007FF;
79
80/*同时支持S3C2410和S3C2440,www.100ask.net*/
81if((gpio->GSTATUS1==0x32410000)||(gpio->GSTATUS1==0x32410002))
82{
83/*FCLK:HCLK:PCLK=1:2:4*/
84clk_power->CLKDIVN=S3C2410_CLKDIV;
85
86/*修改为异步总线模式*/
87__asm__("mrcp15,0,r1,c1,c0,0\n"/*readctrlregister*/
88"orrr1,r1,#0xc0000000\n"/*Asynchronous*/
89"mcrp15,0,r1,c1,c0,0\n"/*writectrlregister*/
90:::"r1"
91);
92
93/*设置PLL锁定时间*/
94clk_power->LOCKTIME=0xFFFFFF;
95
96/*配置MPLL*/
97clk_power->MPLLCON=S3C2410_MPLL_200MHZ;
98
99/*配置MPLL后,要延时一段时间再配置UPLL*/
100delay(4000);
101
102/*配置UPLL*/
103clk_power->UPLLCON=S3C2410_UPLL_48MHZ;
104
105/*再延时一会*/
106delay(8000);
107
108/*机器类型ID,这在调用Linux内核时用到*/
109gd->bd->bi_arch_number=MACH_TYPE_SMDK2410;
110}
111else
112{
113/*FCLK:HCLK:PCLK=1:4:8*/
114clk_power->CLKDIVN=S3C2440_CLKDIV;
115
116/*修改为异步总线模式*/
117__asm__("mrcp15,0,r1,c1,c0,0\n"/*readctrlregister*/
118"orrr1,r1,#0xc0000000\n"/*Asynchronous*/
119"mcrp15,0,r1,c1,c0,0\n"/*writectrlregister*/
120:::"r1"
121);
122
123/*设置PLL锁定时间*/
124clk_power->LOCKTIME=0xFFFFFF;
125
126/*配置MPLL*/
127clk_power->MPLLCON=S3C2440_MPLL_400MHZ;
128
129/*配置MPLL后,要延时一段时间再配置UPLL*/
130delay(4000);
131
132/*配置UPLL*/
133clk_power->UPLLCON=S3C2440_UPLL_48MHZ;
134
135/*再延时一会*/
136delay(8000);
137
138/*机器类型ID,这在调用Linux内核时用到,这个值要与内核相对应*/
139gd->bd->bi_arch_number=MACH_TYPE_S3C2440;
140}
141
142/*启动内核时,参数存放位置。这个值在构造标记列表时用到*/
143gd->bd->bi_boot_params=0x30000100;
144
145icache_enable();
146dcache_enable();
147
148return0;
149}
150
最后一步:获取系统时钟的函数需要针对S3C2410、S3C2440的不同进行修改。
在后面设置串口波特率时需要获得系统时钟,就是在U-Boot的第二阶
段,lib_arm/board.c中start_armboot函数调用serial_init函数初始化串口时,会调用get_PCLK函数。它在
cpu/arm920t/s3c24x0/speed.c中定义,与它相关的还有get_HCLK、get_PLLCLK等函数。
前面的board_init函数在识别出S3C2410或S3C2440后,设置了机器类型
ID:gd->bd->bi_arch_number,后面的函数可以通过它来分辨是S3C2410还是S3C2440。首先要在程序的开头
增加如下一行,这样才可以使用gd变量:
DECLARE_GLOBAL_DATA_PTR;
S3C2410和S3C2440的MPLL、UPLL计算公式不一样,所以get_PLLCLK函数也需要修改:
56staticulongget_PLLCLK(intpllreg)
57{
58S3C24X0_CLOCK_POWER*constclk_power=S3C24X0_GetBase_CLOCK_POWER();
59ulongr,m,p,s;
60
61if(pllreg==MPLL)
62r=clk_power->MPLLCON;
63elseif(pllreg==UPLL)
64r=clk_power->UPLLCON;
65else
66hang();
67
68m=((r&0xFF000)>>12)+8;
69p=((r&0x003F0)>>4)+2;
70s=r&0x3;
71
72/*同时支持S3C2410和S3C2440,bywww.100ask.net*/
73if(gd->bd->bi_arch_number==MACH_TYPE_SMDK2410)
74return((CONFIG_SYS_CLK_FREQ*m)/(p<<s));
75else
76return((CONFIG_SYS_CLK_FREQ*m*2)/(p<<s));/*S3C2440*/
77}
78
由于分频系数的设置方法也不一样,get_HCLK、get_PCLK也需要修改。对于S3C2410,沿用原来的计算方法,else分支中是S3C2440的代码:
85/*fors3c2440*/
86#defineS3C2440_CLKDIVN_PDIVN(1<<0)
87#defineS3C2440_CLKDIVN_HDIVN_MASK(3<<1)
88#defineS3C2440_CLKDIVN_HDIVN_1(0<<1)
89#defineS3C2440_CLKDIVN_HDIVN_2(1<<1)
90#defineS3C2440_CLKDIVN_HDIVN_4_8(2<<1)
91#defineS3C2440_CLKDIVN_HDIVN_3_6(3<<1)
92#defineS3C2440_CLKDIVN_UCLK(1<<3)
93
94#defineS3C2440_CAMDIVN_CAMCLK_MASK(0xf<<0)
95#defineS3C2440_CAMDIVN_CAMCLK_SEL(1<<4)
96#defineS3C2440_CAMDIVN_HCLK3_HALF(1<<8)
97#defineS3C2440_CAMDIVN_HCLK4_HALF(1<<9)
98#defineS3C2440_CAMDIVN_DVSEN(1<<12)
99
100/*returnHCLKfrequency*/
101ulongget_HCLK(void)
102{
103S3C24X0_CLOCK_POWER*constclk_power=S3C24X0_GetBase_CLOCK_POWER();
104unsignedlongclkdiv;
105unsignedlongcamdiv;
106inthdiv=1;
107
108/*同时支持S3C2410和S3C2440,bywww.100ask.net*/
109if(gd->bd->bi_arch_number==MACH_TYPE_SMDK2410)
110return((clk_power->CLKDIVN&0x2)?get_FCLK()/2:get_FCLK());
111else
112{
113clkdiv=clk_power->CLKDIVN;
114camdiv=clk_power->CAMDIVN;
115
116/*计算分频比*/
117
118switch(clkdiv&S3C2440_CLKDIVN_HDIVN_MASK){
119caseS3C2440_CLKDIVN_HDIVN_1:
120hdiv=1;
121break;
122
123caseS3C2440_CLKDIVN_HDIVN_2:
124hdiv=2;
125break;
126
127caseS3C2440_CLKDIVN_HDIVN_4_8:
128hdiv=(camdiv&S3C2440_CAMDIVN_HCLK4_HALF)?8:4;
129break;
130
131caseS3C2440_CLKDIVN_HDIVN_3_6:
132hdiv=(camdiv&S3C2440_CAMDIVN_HCLK3_HALF)?6:3;
133break;
134}
135
136returnget_FCLK()/hdiv;
137}
138}
139
140/*returnPCLKfrequency*/
141ulongget_PCLK(void)
142{
143S3C24X0_CLOCK_POWER*constclk_power=S3C24X0_GetBase_CLOCK_POWER();
144unsignedlongclkdiv;
145unsignedlongcamdiv;
146inthdiv=1;
147
148/*同时支持S3C2410和S3C2440,bywww.100ask.net*/
149if(gd->bd->bi_arch_number==MACH_TYPE_SMDK2410)
150return((clk_power->CLKDIVN&0x1)?get_HCLK()/2:get_HCLK());
151else
152{
153clkdiv=clk_power->CLKDIVN;
154camdiv=clk_power->CAMDIVN;
155
156/*计算分频比*/
157
158switch(clkdiv&S3C2440_CLKDIVN_HDIVN_MASK){
159caseS3C2440_CLKDIVN_HDIVN_1:
160hdiv=1;
161break;
162
163caseS3C2440_CLKDIVN_HDIVN_2:
164hdiv=2;
165break;
166
167caseS3C2440_CLKDIVN_HDIVN_4_8:
168hdiv=(camdiv&S3C2440_CAMDIVN_HCLK4_HALF)?8:4;
169break;
170
171caseS3C2440_CLKDIVN_HDIVN_3_6:
172hdiv=(camdiv&S3C2440_CAMDIVN_HCLK3_HALF)?6:3;
173break;
174}
175
176returnget_FCLK()/hdiv/((clkdiv&S3C2440_CLKDIVN_PDIVN)?2:1);
177}
178}
179
现在重新执行“make100ask24x0_config”和“make
all”生成的u-boot.bin文件既可以运行于S3C2410开发板,也可以运行于S3C2440开发板。将它烧入NOR
Flash后启动,就可以在串口工具(设置为115200,8N1)中看到提示信息,可以输入各种命令操作U-Boot了。
(4)选择NORFlash的型号。
但是,现在还无法通过U-Boot命令烧写NORFlash。本书所用开发板中的NORFlash型号为AM29LV800,而配置文件include/configs/100ask24x0.h中的默认型号为AM29LV400。修改如下:
#defineCONFIG_AMD_LV4001/*uncommentthisifyouhaveaLV400flash*/
#if0
#defineCONFIG_AMD_LV8001/*uncommentthisifyouhaveaLV800flash*/
#endif
改为:
#if0
#defineCONFIG_AMD_LV4001/*uncommentthisifyouhaveaLV400flash*/
#endif
#defineCONFIG_AMD_LV8001/*uncommentthisifyouhaveaLV800flash*/
本例中NOR
Flash的操作函数在board/100ask24x0/flash.c中实现,它支持AM29LV400y和AM29LV800。对于其他型号的
NOR
Flash,如果符合CFI接口标准,则可以在使用drivers/cfi_flash.c中的接口函数;否则,只好自己编写了。如果要使用
cfi_flash.c,如下修改两个文件:
在include/configs/100ask24x0.h中增加以下一行:
#defineCFG_FLASH_CFI_DRIVER1
在board/100ask24x0/Makefile中去掉flash.o:
COBJS:=100ask24x0.oflash.o
改为:
COBJS:=100ask24x0.o
修改好对NORFlash的支持后,重新编译U-Boot:makeclean、makeall。运行后可以在串口中看到如下字样:
Flash:1MB
现在可以使用loadb、loady等命令通过串口下载文件,然后使用erase、cp命令分别擦除、烧写NORFlash了,它们的效率比JTAG快上好几倍。
2.支持串口xmodem协议
上面的loadb命令需要配合Linux下的kermit工具来使用,loady命令通过串
口ymodem协议来传输文件。Windows下的超级终端虽然支持ymodem,但是它的使用界面实在不友好。而本书推荐使用的Windows工具
SecureCRT只支持xmodem和zmodem。为了方便在Windows下开发,现在修改代码增加对xmodem的支持,即增加一个命令
loadx。
依照loady的实现来编写代码,首先使用U_BOOT_CMD宏来增加loadx命令:
/*支持xmodem,www.100ask.net*/
U_BOOT_CMD(
loadx,3,0,do_load_serial_bin,
"loadx-loadbinaryfileoverserialline(xmodemmode)\n",
"[off][baud]\n"
"-loadbinaryfileoverserialline"
"withoffset'off'andbaudrate'baud'\n"
);
其次,在do_load_serial_bin函数中增加对loadx命令的处理分支。也是依照loady来实现:
481/*支持xmodem,www.100ask.net*/
482if(strcmp(argv[0],"loadx")==0){
483printf("##Readyforbinary(xmodem)download"
484"to0x%08lXat%dbps...\n",
485offset,
486load_baudrate);
487
488addr=load_serial_xmodem(offset);
489
490}elseif(strcmp(argv[0],"loady")==0){
491printf("##Readyforbinary(ymodem)download"
492"to0x%08lXat%dbps...\n",
……
第481~490行就是为loadx命令增加的代码。
在第288行调用load_serial_xmodem函数,它是依照load_serial_ymodem实现的一个新函数:
36#if(CONFIG_COMMANDS&CFG_CMD_LOADB)
37/*支持xmodem,www.100ask.net*/
38staticulongload_serial_xmodem(ulongoffset);
39staticulongload_serial_ymodem(ulongoffset);
40#endif
……
995/*支持xmodem,www.100ask.net*/
996staticulongload_serial_xmodem(ulongoffset)
997{
……
1003charxmodemBuf[1024];/*原来是ymodemBuf,这只是为了与函数名称一致*/
……
1008info.mode=xyzModem_xmodem;/*原来是xyzModem_ymodem,对应ymodem*/
……
首先在文件开头增加load_serial_xmodem函数的声明,然后复制load_serial_ymodem函数为load_serial_xmodem,稍作修改:
①将局部数组ymodemBuf改名为xmodemBuf,并在后面使用到的地方统一修改。这只是为了与函数名称一致。
②info.mode的值从xyzModem_ymodem改为xyzModem_xmodem。
重新编译、烧写u-boot.bin后,就可以使用loadx命令下载文件了。
3.支持网卡芯片CS8900
使用串口来传输文件的速率太低,现在增加对网卡芯片CS8900的支持。
本书使用开发板的网卡芯片CS8900的连接方式与smdk2410完全一样,所以现在的
U-Boot中已经支持CS8900了,它的驱动程序为drivers/cs8900.c。只要在U-Boot控制界面中稍加配置就可以使用网络功能。使
用网络之前,先设置开发板IP地址、MAC地址,服务器IP地址,比如可以在U-Boot中执行以下命令:
setenvipaddr192.168.1.17
setenvethaddr08:00:3e:26:0a:5b
setenvserverip192.168.1.11
saveenv
然后就可以使用tftp或nfs命令下载文件了,注意:服务器上要开启tftp或nfs服务。比如可以使用如下命令将u-boot.bin文件下载到内存0x30000000中:
tftp0x30000000u-boot.bin
或
nfs0x30000000192.168.1.57:/work/nfs_root/u-boot.bin
可以修改配置文件,让网卡的各个默认值就是上面设置的值。在此之前,先了解网卡的相关文件,这有助于移植代码以支持其他连接方式的CS8900。
首先,CS8900接在S3C2410、S3C2440的BANK3,位宽为16,使用WAIT、nBE信号。在设置存储控制器时要设置好BANK3。代码在board/100ask24x0/lowlevel_init.S中:
#defineB3_BWSCON(DW16+WAIT+UBLB)
……
/*时序参数*/
#defineB3_Tacs0x0/*0clk*/
#defineB3_Tcos0x3/*4clk*/
#defineB3_Tacc0x7/*14clk*/
#defineB3_Tcoh0x1/*1clk*/
#defineB3_Tah0x0/*0clk*/
#defineB3_Tacp0x3/*6clk*/
#defineB3_PMC0x0/*normal*/
接下来,还要确定CS8900的基地址。这在配置文件include/configs/100ask24x0.h中定义:
#defineCONFIG_DRIVER_CS89001/*使用CS8900*/
#defineCS8900_BASE0x19000300/*基地址*/
#defineCS8900_BUS161/*位宽为16*/
从第6章可以知道网卡CS8900的访问基址为0x19000000,之所以再偏移0x300是由它的特性决定的。
最后,还是在配置文件include/configs/100ask24x0.h中定义CS8900的各个默认地址:
#defineCONFIG_ETHADDR08:00:3e:26:0a:5b
#defineCONFIG_NETMASK255.255.255.0
#defineCONFIG_IPADDR192.168.1.17
#defineCONFIG_SERVERIP192.168.1.11
额外的,如果要增加ping命令,还可以在配置文件include/configs/100ask24x0.h的宏CONFIG_COMMANDS中增加CFG_CMD_PING,如下:
#defineCONFIG_COMMANDS\
(CONFIG_CMD_DFL|\
CFG_CMD_CACHE|\
CFG_CMD_PING|\
……
4.支持NANDFlash
U-Boot1.1.6中对NAND
Flash的支持有新旧两套代码,新代码在drivers/nand目录下,旧代码在drivers/nand_legacy目录下。文档doc
/README.nand对这两套代码有所说明:使用旧代码需要定义更多的宏,而新代码移植自Linux内核2.6.12,它更加智能,可以自动识别更多
型号的NAND
Flash。目前之所以还保留旧的代码,是因为两个目标板NETTA、NETTA_ISDN使用JFFS文件系统,它们还依赖于旧代码。当相关功能移植到
新代码之后,旧的代码将从U-Boot中去除。
要让U-Boot支持NANDFlash,首先在配置文件include/configs/100ask24x0.h的宏CONFIG_COMMANDS中增加CFG_CMD_NAND,如下:
#defineCONFIG_COMMANDS\
(CONFIG_CMD_DFL|\
CFG_CMD_CACHE|\
CFG_CMD_PING|\
CFG_CMD_NAND|\
……
然后选择使用哪套代码:在配置文件中定义宏CFG_NAND_LEGACY则使用旧代码,否则使用新代码。
使用旧代码时,需要实现drivers/nand_legacy/nand_legacy.c中使用到的各种宏,比如:
#defineNAND_WAIT_READY(nand)/*等待NandFlash的状态为“就绪”,代码依赖于具体的开发板*/
#defineWRITE_NAND_COMMAND(d,adr)/*写NANDFlash命令,代码依赖于具体的开发板*/
本书使用新代码,下面讲述移植过程。
代码的移植没有现成的文档,可以在配置文件include/configs/100ask24x0.h的宏CONFIG_COMMANDS中增加CFG_CMD_NAND后,就编译代码,然后一个一个地解决出现的错误。编译结果中出现的错误和警告如下:
nand.h:412:error:`NAND_MAX_CHIPS'undeclaredhere(notinafunction)
nand.c:35:error:`CFG_MAX_NAND_DEVICE'undeclaredhere(notinafunction)
nand.c:38:error:`CFG_NAND_BASE'undeclaredhere(notinafunction)
nand.c:35:error:storagesizeof`nand_info'isn'tknown
nand.c:37:error:storagesizeof`nand_chip'isn'tknown
nand.c:38:error:storagesizeof`base_address'isn'tknown
nand.c:37:warning:'nand_chip'definedbutnotused
nand.c:38:warning:'base_address'definedbutnotused
在配置文件include/configs/100ask24x0.h中增加如下3个宏就可
以解决上述错误。在Flash的驱动程序中,设备是逻辑上的概念,表示一组相同结构、访问函数相同的Flash芯片。在本书所用开发板中,只有一个
NANDFlash芯片,所以设备数为1,芯片数也为1。
#defineCFG_NAND_BASE0/*无实际意义:基地址,这在board_nand_init中重新指定*/
#defineCFG_MAX_NAND_DEVICE1/*NANDFlash“设备”的数目为1*/
#defineNAND_MAX_CHIPS1/*每个NANDFlash“设备”由1个NANDFlash“芯片”组成*/
修改配置文件后再次编译,现在只有一个错误了,“board_nand_init函数未定义”:
nand.c:50:undefinedreferenceto`board_nand_init'
调用board_nand_init函数的过程为:NAND
Flash的初始化入口函数是nand_init,它在lib_arm/board.c的start_armboot函数中被调用;nand_init函
数在drivers/nand/nand.c中实现,它调用相同文件中的nand_init_chip函数;nand_init_chip函数首先调用
board_nand_init函数来初始化NANDFlash设备,最后才是统一的识别过程。
从board_nand_init函数的名称就可以知道它是平台/开发板相关的函数,需要自
己编写。本书在cpu/arm920t/s3c24x0目录下新建一个文件nand_flash.c,在里面针对S3C2410、S3C2440实现了统
一的board_nand_init函数。
在编写board_nand_init函数的之前,需要针对S3C2410、S3C2440NANDFlash控制器的不同定义一些数据结构和函数:
(1)在include/s3c24x0.h文件中增加S3C2440_NAND数据结构。
/*NANDFLASH(seeS3C2440manualchapter6,www.100ask.net)*/
typedefstruct{
S3C24X0_REG32NFCONF;
S3C24X0_REG32NFCONT;
S3C24X0_REG32NFCMD;
S3C24X0_REG32NFADDR;
S3C24X0_REG32NFDATA;
S3C24X0_REG32NFMECCD0;
S3C24X0_REG32NFMECCD1;
S3C24X0_REG32NFSECCD;
S3C24X0_REG32NFSTAT;
S3C24X0_REG32NFESTAT0;
S3C24X0_REG32NFESTAT1;
S3C24X0_REG32NFMECC0;
S3C24X0_REG32NFMECC1;
S3C24X0_REG32NFSECC;
S3C24X0_REG32NFSBLK;
S3C24X0_REG32NFEBLK;
}/*__attribute__((__packed__))*/S3C2440_NAND;
(2)在include/s3c2410.h文件中仿照S3C2410_GetBase_NAND函数定义S3C2440_GetBase_NAND函数。
/*fors3c2440,www.100ask.net*/
staticinlineS3C2440_NAND*constS3C2440_GetBase_NAND(void)
{
return(S3C2440_NAND*const)S3C2410_NAND_BASE;
}
既然新的NAND
Flash代码是从Linux内核2.6.12中移植来的,那么cpu/arm920t/s3c24x0/nand_flash.c文件也可以仿照内核
中,对S3C2410、S3C2440的NAND
Flash进行初始化的drivers/mtd/nand/s3c2410.c文件来编写。为了方便阅读,先把cpu/arm920t/s3c24x0
/nand_flash.c文件的代码全部列出来,再讲解:
01/*
02*s3c2410/s3c2440的NANDFlash控制器接口,www.100ask.net
03*修改自Linux内核2.6.13文件drivers/mtd/nand/s3c2410.c
04*/
05
06#include<common.h>
07
08#if(CONFIG_COMMANDS&CFG_CMD_NAND)&&!defined(CFG_NAND_LEGACY)
09#include<s3c2410.h>
10#include<nand.h>
11
12DECLARE_GLOBAL_DATA_PTR;
13
14#defineS3C2410_NFSTAT_READY(1<<0)
15#defineS3C2410_NFCONF_nFCE(1<<11)
16
17#defineS3C2440_NFSTAT_READY(1<<0)
18#defineS3C2440_NFCONT_nFCE(1<<1)
19
20
21/*S3C2410:NANDFlash的片选函数*/
22staticvoids3c2410_nand_select_chip(structmtd_info*mtd,intchip)
23{
24S3C2410_NAND*consts3c2410nand=S3C2410_GetBase_NAND();
25
26if(chip==-1){
27s3c2410nand->NFCONF|=S3C2410_NFCONF_nFCE;/*禁止片选信号*/
28}else{
29s3c2410nand->NFCONF&=~S3C2410_NFCONF_nFCE;/*使能片选信号*/
30}
31}
32
33/*S3C2410:命令和控制函数
34*
35*注意,这个函数仅仅根据各种命令来修改“写地址”IO_ADDR_W的值(这称为tglx方法),
36*这种方法使得平台/开发板相关的代码很简单。
37*真正发出命令是在上一层NANDFlash的统一的驱动中实现,
38*它首先调用这个函数修改“写地址”,然后才分别发出控制、地址、数据序列。
39*/
40staticvoids3c2410_nand_hwcontrol(structmtd_info*mtd,intcmd)
41{
42S3C2410_NAND*consts3c2410nand=S3C2410_GetBase_NAND();
43structnand_chip*chip=mtd->priv;
44
45switch(cmd){
46caseNAND_CTL_SETNCE:
47caseNAND_CTL_CLRNCE:
48printf("%s:calledforNCE\n",__FUNCTION__);
49break;
50
51caseNAND_CTL_SETCLE:
52chip->IO_ADDR_W=(void*)&s3c2410nand->NFCMD;
53break;
54
55caseNAND_CTL_SETALE:
56chip->IO_ADDR_W=(void*)&s3c2410nand->NFADDR;
57break;
58
59/*NAND_CTL_CLRCLE:*/
60/*NAND_CTL_CLRALE:*/
61default:
62chip->IO_ADDR_W=(void*)&s3c2410nand->NFDATA;
63break;
64}
65}
66
67/*S3C2410:查询NANDFlash状态
68*
69*返回值:0–忙,1–就绪
70*/
71staticints3c2410_nand_devready(structmtd_info*mtd)
72{
73S3C2410_NAND*consts3c2410nand=S3C2410_GetBase_NAND();
74
75return(s3c2410nand->NFSTAT&S3C2410_NFSTAT_READY);
76}
77
78
79/*S3C2440:NANDFlash的片选函数*/
80staticvoids3c2440_nand_select_chip(structmtd_info*mtd,intchip)
81{
82S3C2440_NAND*consts3c2440nand=S3C2440_GetBase_NAND();
83
84if(chip==-1){
85s3c2440nand->NFCONT|=S3C2440_NFCONT_nFCE;/*禁止片选信号*/
86}else{
87s3c2440nand->NFCONT&=~S3C2440_NFCONT_nFCE;/*使能片选信号*/
88}
89}
90
91/*S3C2440:命令和控制函数,与s3c2410_nand_hwcontrol函数类似*/
92staticvoids3c2440_nand_hwcontrol(structmtd_info*mtd,intcmd)
93{
94S3C2440_NAND*consts3c2440nand=S3C2440_GetBase_NAND();
95structnand_chip*chip=mtd->priv;
96
97switch(cmd){
98caseNAND_CTL_SETNCE:
99caseNAND_CTL_CLRNCE:
100printf("%s:calledforNCE\n",__FUNCTION__);
101break;
102
103caseNAND_CTL_SETCLE:
104chip->IO_ADDR_W=(void*)&s3c2440nand->NFCMD;
105break;
106
107caseNAND_CTL_SETALE:
108chip->IO_ADDR_W=(void*)&s3c2440nand->NFADDR;
109break;
110
111/*NAND_CTL_CLRCLE:*/
112/*NAND_CTL_CLRALE:*/
113default:
114chip->IO_ADDR_W=(void*)&s3c2440nand->NFDATA;
115break;
116}
117}
118
119/*S3C2440:查询NANDFlash状态
120*
121*返回值:0–忙,1–就绪
122*/
123staticints3c2440_nand_devready(structmtd_info*mtd)
124{
125S3C2440_NAND*consts3c2440nand=S3C2440_GetBase_NAND();
126
127return(s3c2440nand->NFSTAT&S3C2440_NFSTAT_READY);
128}
129
130/*
131*Nandflash硬件初始化:
132*设置NANDFlash的时序,使能NANDFlash控制器
133*/
134staticvoids3c24x0_nand_inithw(void)
135{
136S3C2410_NAND*consts3c2410nand=S3C2410_GetBase_NAND();
137S3C2440_NAND*consts3c2440nand=S3C2440_GetBase_NAND();
138
139#defineTACLS0
140#defineTWRPH04
141#defineTWRPH12
142
143if(gd->bd->bi_arch_number==MACH_TYPE_SMDK2410)
144{
145/*使能NANDFlash控制器,初始化ECC,使能片选信号,设置时序*/
146s3c2410nand->NFCONF=(1<<15)|(1<<12)|(1<<11)|(TACLS<<8)|(TWRPH0<<4)|(TWRPH1<<0);
147}
148else
149{
150/*设置时序*/
151s3c2440nand->NFCONF=(TACLS<<12)|(TWRPH0<<8)|(TWRPH1<<4);
152/*初始化ECC,使能NANDFlash控制器,使能片选信号*/
153s3c2440nand->NFCONT=(1<<4)|(0<<1)|(1<<0);
154}
155}
156
157/*
158*被drivers/nand/nand.c调用,初始化NANDFlash硬件,初始化访问接口函数
159*/
160voidboard_nand_init(structnand_chip*chip)
161{
162S3C2410_NAND*consts3c2410nand=S3C2410_GetBase_NAND();
163S3C2440_NAND*consts3c2440nand=S3C2440_GetBase_NAND();
164
165s3c24x0_nand_inithw();/*Nandflash硬件初始化*/
166
167if(gd->bd->bi_arch_number==MACH_TYPE_SMDK2410){
168chip->IO_ADDR_R=(void*)&s3c2410nand->NFDATA;
169chip->IO_ADDR_W=(void*)&s3c2410nand->NFDATA;
170chip->hwcontrol=s3c2410_nand_hwcontrol;
171chip->dev_ready=s3c2410_nand_devready;
172chip->select_chip=s3c2410_nand_select_chip;
173chip->options=0;/*设置位宽等,位宽为8*/
174}else{
175chip->IO_ADDR_R=(void*)&s3c2440nand->NFDATA;
176chip->IO_ADDR_W=(void*)&s3c2440nand->NFDATA;
177chip->hwcontrol=s3c2440_nand_hwcontrol;
178chip->dev_ready=s3c2440_nand_devready;
179chip->select_chip=s3c2440_nand_select_chip;
180chip->options=0;/*设置位宽等,位宽为8*/
181}
182
183chip->eccmode=NAND_ECC_SOFT;/*ECC较验方式:软件ECC*/
184}
185
186#endif
文件中分别针对S3C2410、S3C2440实现了NAND
Flash最底层访问函数,并进行了一些硬件的设置(比如时序、使能NANDFlash控制器等)。新的代码对NAND
Flash的封装做得很好,只要向上提供底层初始化函数board_nand_init来设置好平台/开发板相关的初始化、提供底层接口即可。
最后,只要将新建的nand_flash.c文件编入U-Boot中就可以擦除、读写NANDFlash了。如下修改cpu/arm920t/s3c24x0/Makefile文件即可:
COBJS=i2c.ointerrupts.oserial.ospeed.o\
usb_ohci.o
改为:
COBJS=i2c.ointerrupts.oserial.ospeed.o\
usb_ohci.onand_flash.o
现在,可以使用新编译的u-boot.bin烧写内核映像到NANDFlash去了,请参考15.2.6。
5.支持烧写yaffs文件系统映像
在实际生产中,可以通过烧片器等手段将内核、文件系统映像烧入固态存储设备中,Bootloader不需要具备烧写功能。但为了方便开发,通常在Bootloader中增加烧写内核、文件系统映像文件的功能。
增加了NANDFlash功能的U-Boot1.1.6已经可以通过“nand
write……”、“nandwrite.jffs2……”等命令来烧写内核,cramfs、jffs2文件系统映像文件。但是在NAND
Flash上,yaffs文件系统的性能更佳,下面增加“nandwrite.yaffs……”命令以烧写yaffs文件系统映像文件。
“nandwrite.yaffs……”字样的命令中,“nand”是具体命令,“write.yaffs……”是参数。nand命令在common/cmd_nand.c中实现:
U_BOOT_CMD(nand,5,1,do_nand,
"nand-NANDsub-system\n",
"info-showavailableNANDdevices\n"
"nanddevice[dev]-showorsetcurrentdevice\n"
"nandread[.jffs2]-addroff|partitionsize\n"
"nandwrite[.jffs2]-addroff|partitonsize-read/write`size'bytesstarting\n"
"atoffset`off'to/frommemoryaddress`addr'\n"
……
先在其中增加“nandwrite.yaffs……”的使用说明:
U_BOOT_CMD(nand,5,1,do_nand,
"nand-NANDsub-system\n",
"info-showavailableNANDdevices\n"
"nanddevice[dev]-showorsetcurrentdevice\n"
"nandread[.jffs2]-addroff|partitionsize\n"
"nandwrite[.jffs2]-addroff|partitonsize-read/write`size'bytesstarting\n"
"atoffset`off'to/frommemoryaddress`addr'\n"
"nandread.yaffsaddroffsize-readthe`size'byteyaffsimagestarting\n"
"atoffset`off'tomemoryaddress`addr'\n"
"nandwrite.yaffsaddroffsize-writethe`size'byteyaffsimagestarting\n"
"atoffset`off'frommemoryaddress`addr'\n"
……
然后,在nand命令的处理函数do_nand中增加对“write.yaffs……”的支持。do_nand函数仍在common/cmd_nand.c中实现,代码修改如下:
331(!strcmp(s,".jffs2")||!strcmp(s,".e")||!strcmp(s,".i"))){
……
354}elseif(s!=NULL&&!strcmp(s,".yaffs")){
355if(read){
356/*read*/
357nand_read_options_topts;
358memset(&opts,0,sizeof(opts));
359opts.buffer=(u_char*)addr;
360opts.length=size;
361opts.offset=off;
362opts.readoob=1;
363opts.quiet=quiet;
364ret=nand_read_opts(nand,&opts);
365}else{
366/*write*/
367nand_write_options_topts;
368memset(&opts,0,sizeof(opts));
369opts.buffer=(u_char*)addr;/*yaffs文件系统映像存放的地址*/
370opts.length=size;/*长度*/
371opts.offset=off;/*要烧写到的NANDFlash的偏移地址*/
372/*opts.forceyaffs=1;*//*计算ECC码的方法,没有使用*/
373opts.noecc=1;/*不需要计算ECC,yaffs映像中有OOB数据*/
374opts.writeoob=1;/*写OOB区*/
375opts.blockalign=1;/*每个“逻辑上的块”大小为1个“物理块”*/
376opts.quiet=quiet;/*是否打印提示信息*/
377opts.skipfirstblk=1;/*跳过第一个可用块*/
378ret=nand_write_opts(nand,&opts);
379}
380}else{
……
385}
386
第354~379行就是针对命令“nandread.yaffs……”、“nand
write.yaffs……”增加的代码。有兴趣的读者可以自己分析“if(read)”分支的代码,下面只讲解“else”分支,即“nand
write.yaffs……”命令的实现。
NANDFlash每一页大小为(512+16)字节(还有其他格式的NAND
Flash,比如每页大小为(256+8)、(2048+64)等),其中的512字节就是一般存储数据的区域,16字节称为OOB(OutOf
Band)区。通常在OOB区存放坏块标记、前面512字节的ECC较验码等。
cramfs、jffs2文件系统映像文件中并没有OOB区的内容,如果将它们烧入NOR
Flash中,则是简单的“平铺”关系;如果将它们烧入NANDFlash中,则NAND
Flash的驱动程序首先根据OOB的标记略过坏块,然后将一页数据(512字节)写入后,还会计算这512字节的ECC较验码,最后将它写入OOB区,
如此循环。cramfs、jffs2文件系统映像文件的大小通常是512的整数倍。
而yaffs文件系统映像文件的格式则跟它们不同,文件本身就包含了OOB区的数据(里面有
坏块标记、ECC较验码、其他yaffs相关的信息)。所以烧写时,不需要再计算ECC值,首先检查是否坏块(是则跳过),然后写入512字节的数据,最
后写入16字节的OOB数据,如此循环。yaffs文件系统映像文件的大小是(512+16)的整数倍。
注意:烧写yaffs文件系统映像时,分区上第一个可用的(不是坏块)块也要跳过。
下面分析上面的代码。
第369~371行设置源地址、目的地址、长度。烧写yaffs文件系统映像前,一般通过网
络将它下载到内存某个地址处(比如0x30000000),然后通过类似“nandwrite.yaffs0x30000000
0x00A00000$(filesize)”的命令烧到NAND
Flash的偏移地址0x00A00000处。对于这个命令,第369行中opts.buffer等于0x30000000,第370行中
opts.length等于$(filesize)的值,就是前面下载的文件的大小,第371行中的opts.offset等于0x00A00000。
这里列出不使用的第372行,是因为opts.forceyaffs这个名字很有欺骗性,它其实是指计算ECC较验码的一种方法。烧写yaffs文件系统映像时,不需要计算ECC较验码。
第373、374行指定烧写数据时不计算ECC较验码、而是烧入文件中的OOB数据。
第375行指定“逻辑块”的大小,“逻辑块”可以由多个“物理块”组成,在yaffs文件系统映像中,它们是1:1的关系。
第377行的opts.skipfirstblk是新加的项,nand_write_options_t结构中没有skipfirstblk成员。它表示烧写时跳过第一个可用的逻辑块──这是由yaffs文件系统的特性决定的。
既然skipfirstblk是在nand_write_options_t结构中新加的项,那么就要重新定义nand_write_options_t结构,并在下面调用的nand_write_opts函数中对它进行处理。
首先在include/nand.h中如下修改,增加skipfirstblk成员:
structnand_write_options{
u_char*buffer;/*memoryblockcontainingimagetowrite*/
ulonglength;/*numberofbytestowrite*/
ulongoffset;/*startaddressinNAND*/
intquiet;/*don'tdisplayprogressmessages*/
intautoplace;/*iftrueuseautoooblayout*/
intforcejffs2;/*forcejffs2ooblayout*/
intforceyaffs;/*forceyaffsooblayout*/
intnoecc;/*writewithoutecc*/
intwriteoob;/*imagecontainsoobdata*/
intpad;/*padtopagesize*/
intblockalign;/*1|2|4setmultipleoferaseblockstoalignto*/
intskipfirstblk;/*新加,烧写时跳过第一个可用的逻辑块*/
};
typedefstructnand_write_optionsnand_write_options_t;
然后,修改nand_write_opts函数增加对skipfirstblk成员的支持。它在drivers/nand/nand_util.c文件中,下面的第301、第430~435行是新加的:
285intnand_write_opts(nand_info_t*meminfo,constnand_write_options_t*opts)
286{
……
300intresult;
301intskipfirstblk=opts->skipfirstblk;
……
430/*skipthefirstgoodblockwhenwirteyaffsimage,bywww.100ask.net*/
431if(skipfirstblk){
432mtdoffset+=erasesize_blockalign;
433skipfirstblk=0;
434continue;
435}
……
进行了上面的移植后,U-Boot已经可以烧yaffs文件系统映像了。由于前面设置“opts.noecc=1”不使用ECC较验码,在烧写过程中会出现很多的提示信息:
WritingdatawithoutECCtoNAND-FLASHisnotrecommended
可以修改drivers/nand/nand_base.c文件的nand_write_page函数将它去掉:
917caseNAND_ECC_NONE:
918printk(KERN_WARNING"WritingdatawithoutECCtoNAND-FLASHisnotrecommended\n");
改为:
917caseNAND_ECC_NONE:
918//printk(KERN_WARNING"WritingdatawithoutECCtoNAND-FLASHisnotrecommended\n");
6.修改默认配置参数以方便使用
前面移植网卡芯片CS8900时,已经设置过默认IP地址等。为了使用U-Boot时减少一些设置,现在修改配置文件include/configs/100ask24x0.h增加默认配置参数,其中一些在移植过程中已经增加的选项这里也再次说明。
(1)Linux启动参数。
增加如下3个宏:
#defineCONFIG_SETUP_MEMORY_TAGS1/*向内核传递内存分布信息*/
#defineCONFIG_CMDLINE_TAG1/*向内核传递命令行参数*/
/*默认命令行参数*/
#defineCONFIG_BOOTARGS"noinitrdroot=/dev/mtdblock2init=/linuxrcconsole=ttySAC0"
(2)自动启动命令。
增加如下2个宏:
/*自动启动前延时3秒*/
#defineCONFIG_BOOTDELAY3
/*自动启动的命令*/
#defineCONFIG_BOOTCOMMAND“nboot0x3200000000;bootm0x32000000”
自动启动时(开机3秒内无输入),首先执行“nboot0x3200000000”命令将第0个NANDFlash偏移地址0上的映像文件复制到内存0x32000000中;然后执行“bootm0x32000000”命令启动内存中的映像。
(3)默认网络设置。
根据具体网络环境增加、修改下面4个宏:
#defineCONFIG_ETHADDR08:00:3e:26:0a:5b
#defineCONFIG_NETMASK255.255.255.0
#defineCONFIG_IPADDR192.168.1.17
#defineCONFIG_SERVERIP192.168.1.11
2.6U-Boot的常用命令
1.U-Boot的常用命令的用法
进入U-Boot控制界面后,可以运行各种命令,比如下载文件到内存,擦除、读写Flash,运行内存、NORFlash、NANDFlash中的程序,查看、修改、比较内存中的数据等。
使用各种命令时,可以使用其开头的若干个字母代替它。比如tftpboot命令,可以使用t、tf、tft、tftp等字母代替,只要其他命令不以这些字母开头即可。
当运行一个命令之后,如果它是可重复执行的(代码中使用U_BOOT_CMD定义这个命令时,第3个参数是1),若想再次运行可以直接输入回车。
U-Boot接受的数据都是16进制,输入时可以省略前缀0x、0X。
下面介绍常用的命令:
(1)帮助命令help。
运行help命令可以看到U-Boot中所有命令的作用,如果要查看某个命令的使用方法,运行“help命令名”,比如“helpbootm”。
可以使用“?”来代替“help”,比如直接输入“?”、“?bootm”。
(2)下载命令。
U-Boot支持串口下载、网络下载,相关命令有:loadb、loads、loadx、loady和tftpboot、nfs。
前几个串口下载命令使用方法相似,以loadx命令为例,它的用法为“loadx[off][baud
]”。中括号“[]”表示里面的参数可以省略,off表示文件下载后存放的内存地址,baud表示使用的波特率。如果baud参数省略,则使用当前的波特
率;如果off参数省略,存放的地址为配置文件中定义的宏CFG_LOAD_ADDR。
tftpboot命令使用TFTP协议从服务器下载文件,服务器的IP地址为环境变量
serverip。用法为“tftpboot[loadAddress]
[bootfilename]”,loadAddress表示文件下载后存放的内存地址,bootfilename表示要下载的文件的名称。如果
loadAddress省略,存放的地址为配置文件中定义的宏CFG_LOAD_ADDR;如果bootfilename省略,则使用单板的IP地址构造
一个文件名,比如单板IP为192.168.1.17,则缺省的文件名为C0A80711.img。
nfs命令使用NFS协议下载文件,用法为“nfs[loadAddress]
[hostip
addr:bootfilename]”。loadAddress、bootfilename的意义与tftpboot命令一样,hostip
addr表示服务器的IP地址,默认为环境变量serverip。
下载文件成功后,U-Boot会自动创建或更新环境变量filesize,它表示下载的文件的长度,可以在后续命令中使用“$(filesize)”来引用它。
(3)内存操作命令。
常用的命令有:查看内存命令md、修改内存命令md、填充内存命令mw、拷贝命令cp。这些
命令都可以带上后缀“.b”、“.w”或“.l”,表示以字节、字(2个字节)、双字(4个字节)为单位进行操作。比如“cp.l30000000
310000002”将从开始地址0x30000000处,拷贝2个双字到开始地址为0x31000000的地方。
md命令用法为“md[.b,.w,.l]address[count]”,表示以字节、字或双字(默认为双字)为单位,显示从地址address开始的内存数据,显示的数据个数为count。
mm命令用法为“mm[.b,.w,.l]address”,表示以字节、字或双字(默认为双字)为单位,从地址address开始修改内存数据。执行mm命令后,输入新数据后回车,地址会自动增加,Ctrl+C退出。
mw命令用法为“mw[.b,.w,.l]addressvalue[count]”,表示以字节、字或双字(默认为双字)为单位,往开始地址为address的内存中填充count个数据,数据值为value。
cp命令用法为“cp[.b,.w,.l]sourcetargetcount”,表示以字节、字或双字(默认为双字)为单位,从源地址source的内存拷贝count个数据到目的地址的内存。
(4)NORFlash操作命令。
常用的命令有查看Flash信息的flinfo命令、加/解写保护命令protect、擦除
命令erase。由于NORFlash的接口与一般内存相似,所以一些内存命令可以在NORFlash上使用,比如读NOR
Flash时可以使用md、cp命令,写NORFlash时可以使用cp命令(cp根据地址分辨出是NORFlash,从而调用NOR
Flash驱动完成写操作)。
直接运行“flinfo”即可看到NORFlash的信息,有NORFlash的型号、容量、各扇区的开始地址、是否只读等信息。比如对于本书基于的开发板,flinfo命令的结果如下:
Bank#1:AMD:1xAmd29LV800BB(8Mbit)
Size:1MBin19Sectors
SectorStartAddresses:
00000000(RO)00004000(RO)00006000(RO)00008000(RO)00010000(RO)
00020000(RO)00030000000400000005000000060000
000700000008000000090000000A0000000B0000
000C0000000D0000000E0000000F0000(RO)
其中的RO表示该扇区处于写保护状态,只读。
对于只读的扇区,在擦除、烧写它之前,要先解除写保护。最简单的命令为“protectoffall”,解除所有NORFlash的写保护。
erase命令常用的格式为“erasestart
end”──擦除的地址范围为start至end、“erasestart+len”──擦除的地址范围为start至(start+len
–1),“eraseall”──表示擦除所有NORFlash。
注意:其中的地址范围,刚好是一个扇区的开始地址到另一个(或同一个)扇区的结束地址。比如要擦除Amd29LV800BB的前5个扇区,执行的命令为“erase00x2ffff”,而非“erase00x30000”。
(5)NANDFlash操作命令。
NANDFlash操作命令只有一个:nand,它根据不同的参数进行不同操作,比如擦除、读取、烧写等。
“nandinfo”查看NANDFlash信息。
“nanderase[clean][offsize]”擦除NAND
Flash。加上“clean”时,表示在每个块的第一个扇区的OOB区加写入清除标记;off、size表示要擦除的开始偏移地址和长度,如果省略
off和size,表示要擦除整个NANDFlash。
“nandread[.jffs2]addroffsize”从NANDFlash偏移地址off处读出size个字节的数据,存放到开始地址为addr的内存中。是否加后缀“.jffs”的差别只是读操作时的ECC较验方法不同。
“nandwrite[.jffs2]addroffsize”把开始地址为addr的内存中的size个字节数据,写到NANDFlash的偏移地址off处。是否加后缀“.jffs”的差别只是写操作时的ECC较验方法不同。
“nandread.yaffsaddroffsize”从NANDFlash偏移地址off处读出size个字节的数据(包括OOB区域),存放到开始地址为addr的内存中。
“nandwrite.yaffsaddroffsize”把开始地址为addr的内存中的size个字节数据(其中有要写入OOB区域的数据),写到NANDFlash的偏移地址off处。
“nanddumpoff”,将NANDFlash偏移地址off的一个扇区的数据打印出来,包括OOB数据。
(6)环境变量命令。
“printenv”命令打印全部环境变量,“printenvname1name2...”打印名字为name1、name2、……”的环境变量。
“setenvnamevalue”设置名字为name的环境变量的值为value。
“setenvname”删除名字为name的环境变量。
上面的设置、删除操作只是在内存中进行,“saveenv”将更改后的所有环境变量写入NORFlash中。
(7)启动命令。
不带参数的“boot”、“bootm”命令都是执行环境变量bootcmd所指定的命令。
“bootm[addr[arg
...]]”命令启动存放在地址addr处的U-Boot格式的映像文件(使用U-Boot目录tools下的mkimage工具制作得到),[arg
...]表示参数。如果addr参数省略,映像文件所在地址为配置文件中定义的宏CFG_LOAD_ADDR。
“goaddr[arg...]”与bootm命令类似,启动存放在地址addr处的二进制文件,[arg...]表示参数。
“nboot[[[loadAddr]dev]offset]”命令将NAND
Flash设备dev上偏移地址off处的映像文件复制到内存loadAddr处,然后,如果环境变量autostart的值为“yes”,就启动这个映
像。如果loadAddr参数省略,存放地址为配置文件中定义的宏CFG_LOAD_ADDR;如果dev参数省略,则它的取值为环境变量
bootdevice的值;如果offset参数省略,则默认为0。
2.U-Boot命令使用实例
下面通过一个例子来演示如何使用各种命令烧写内核映像文件、yaffs映像文件,并启动系统。
(1)制作内核映像文件。
对于本书使用的Linux2.6.22.6版本,编译内核时可以直接生成U-Boot格式的映像文件uImage。
对于不能直接生成uImage的内核,制作方法在U-Boot根目录下的README文件中
有说明,假设已经编译好的内核文件为vmlinux,它是ELF格式的。mkimage是U-Boot目录tools下的工具,它在编译U-Boot时自
动生成。执行以下3个命令将内核文件vmlinux制作为U-Boot格式的映像文件uImage,它们首先将vmlinux转换为二进制格式,然后压
缩,最后构造头部信息(里面包含有文件名称、大小、类型、CRC较验码等):
①arm-linux-objcopy-Obinary-R.note-R.comment-Svmlinuxlinux.bin
②gzip-9linux.bin
③mkimage-Aarm-Olinux-Tkernel-Cgzip-a0x30008000-e0x30008000-n"LinuxKernelImage"-dlinux.bin.gzuImage
(2)烧写内核映像文件uImage。
首先将uImage放在主机上的tftp或nfs目录下,确保已经开启tftp或nfs服务。
然后运行如下命令下载文件,擦除、烧写NANDFlash:
①tftp0x30000000uImage或nfs0x30000000192.168.1.57:/work/nfs_root/uImage
②nanderase0x00x00200000
③nandwrite.jffs20x300000000x0$(filesize)
第3条命令之所以使用“nandwrite.jffs2”而不是“nand
write”,是因为前者不要求文件的长度是页对齐的(512字节对齐)。也可以使用“nand
write”,但是需要将命令中的长度参数改为$(filesize)向上进行512取整后的值。比如uImage的大小为1540883,向上进行
512取整后为1541120(即0x178400),可以使用命令“nandwrite0x300000000x0
0x178400”进行烧写。
(3)烧写yaffs文件系统映像。
假设yaffs文件系统映像的文件名为yaffs.img,首先将它放在主机上的tftp或nfs目录下,确保已经开启tftp或nfs服务;然后执行如下命令下载、擦除、烧写:
①tftp0x30000000yaffs.img或nfs0x30000000192.168.1.57:/work/nfs_root/yaffs.img
②nanderase0xA000000x3600000
③nandwrite.yaffs0x300000000xA00000$(filesize)
这时,重启系统,在U-Boot倒数3秒之后,就会自动启动Linux系统。
(4)烧写jffs2文件系统映像。
假设jffs2文件系统映像的文件名为jffs2.img,首先将它放在主机上的tftp或nfs目录下,确保已经开启tftp或nfs服务;然后执行如下命令下载、擦除、烧写:
①tftp0x30000000jffs2.img或nfs0x30000000192.168.1.57:/work/nfs_root/jffs2.img
②nanderase0x2000000x800000
③nandwrite.jffs20x300000000x200000$(filesize)
系统启动后,就可以使用“mount-tjffs2/dev/mtdblock1/mnt”挂接jffs2文件系统。
2.7使用U-Boot来执行程序
在前面的硬件实验中使用JTAG烧写程序到NAND
Flash,烧写过程十分缓慢。如果使用U-Boot来烧写NANDFlash,效率会高很多。烧写二进制文件到NAND
Flash中所使用的命令与上面烧写内核映像文件uImage的过程类似,只是不需要将二进制文件制作成U-Boot格式。
另外,可以将程序下载到内存中,然后使用go命令执行它。假设有一个程序的二进制可执行文件
test.bin,连接地址为0x30000000。首先将它放在主机上的tftp或nfs目录下,确保已经开启tftp或nfs服务;然后将它下载到内
存0x30000000处,最后使用go命令执行它:
①tftp0x30000000test.bin或nfs0x30000000192.168.1.57:/work/nfs_root/test.bin
②go0x30000000
相关文章推荐
- Bootloader之uBoot简介(转)
- Bootloader简介及UBOOT移植 (转)
- Bootloader及u-boot简介/u-boot系统启动流程
- Bootloader之uBoot简介(转)
- Bootloader及u-boot简介
- Bootloader之uBoot简介
- Bootloader之uBoot简介
- 【转】Bootloader之uBoot简介(转)
- Bootloader之uBoot简介(转)
- 嵌入式Linux开发之Bootloader(Boot-XSBase27024)原理简介
- 移植U-Boot之BootLoader简介
- Bootloader及u-boot简介
- spring boot 简介
- spring boot 简介
- 20、Bootloader(4) -- U-Boot第二阶段分析
- SpringBoot基础教程及框架整合(二)----项目结构简介
- (一)SpringBoot之简介和安装插件以及HelloWorld第一个程序
- Bootloader 简介
- U-boot移植之自己写一个简单的bootloader
- U-boot分析与移植(1)----之bootloader分析