解 霏,蒋大明
(北京交通大学 电子信息工程学院,北京 100044)
嵌入式Linux系统通常由引导装载程序 (包括固化在固件中的 boot代码和 BootLoader),Linux内核,文件系统,应用程序这4部分构成。引导装载程序是系统加电后执行的第一段软件代码,在嵌入式系统中整个系统的加载启动任务就完全由 BootLoader来完成[1]。BootLoader严重依赖硬件,需要根据不同的硬件配置进行移植。U-boot是德国DENX小组开发的多种嵌入式CPU的BootLoader程序,支持Linux VxWorks QNX等多种嵌入式系统。笔者通过对U-Boot启动过程的分析并对其修改,使其支持自动识别启动,并在基于S3C2440的硬件平台验证通过,同时正确引导Linux内核。
硬件平台选用的是基于ARM9体系结构,ARM920T的CPU,SOC选用的是三星S3C2440A,同时支持Nor FLASH与Nand FLASH启动。主要硬件资源如表1所示。
U-Boot全称是Universal Bootloader,是遵循GPL条款的开放源码项目。DENX软件工程中心的Wolfgang Denk最早基于8xxROM的源码创建了PCC-Boot工程,并不断添加处理器的支持。后来Sysgo Gmbh把PPCBoot移植到ARM平台上,创建了ARMBoot工程,然后以PPCBoot工程和ARMBoot工程为基础,创建了U-Boot工程[2]。
表1 开发板硬件资源Tab.1 Hardware resource of platform
U-Boot的启动分为阶段 1(stage1)和阶段 2(stage2),阶段1的代码通常在start.S中,由汇编语言写成。阶段2的代码通常用C语言来实现,可以实现复杂功能,阶段2的代码通常在lib_arm/board.c中。启动流程如图1所示。第1阶段完成的工作包括,基本硬件初始化,屏蔽所有中断源,关闭看门狗,设置CPU速度和时钟频率,RAM初始化等。设置堆栈指针,为阶段2的C语言做好准备,拷贝阶段2代码到RAM,跳转到阶段2的C入口点。第2阶段完成的工作包括,初始化本阶段用到的硬件,检测系统内存映射,加载内核映像与根文件系统映像,设置内核启动参数,启动内核[3]。
图1 U-Boot启动流程Fig.1 Flow chart of boot U-Boot
基于S3C2440的开发板支持Nor FLASH和Nand FLASH这两种启动方式,不论从那种方式启动,程序都是从0x00000000地址处开始执行的,不同的地方是地址映射不一样。要使开发板上电后能自动识别启动设备,需要根据2种FLASH启动时的不同点加以区别。
当采用Nor FLASH这种启动时,Nor FLASH被映射到nGCS0(0x00000000)中,S3C2440可以直接从 0x00000000运行。
当采用Nand FLASH这种启动时,S3C2440的Nand FLASH控制器会自动把Nand FLASH中前4K数据被拷贝到S3C2440内部大小为4 k的 SRAM(Steppingstone)中,同时内部的SRAM被映射到nGCS0(0x00000000)和0x40000000~0x40001000地址空间。S3C2440从0x00000000开始执行[4]。
S3C2440的启动方式可以由引脚OM1与OM0决定,这2个引脚的逻辑电平是实现U-Boot自动识别启动的关键。OM功能如表2所示。
表2OM功能Tab.2 Function of OM
当OM[1:0]=00时,S3C2440从 Nand FLASH启动,其他情况则S3C2440从Nor FLASH启动,所以可以通过检测OM[1:0]的电平来判定S3C2440的启动方式。寄存器rBWSCON的[2:1]位是由OM[1:0]电平状态决定的,因此可以通过检测寄存rBWSCON[2:1]的状态来识别启动方式。
笔者将AUTO_DETECT函数添加到start.S文件中,这个函数可以实现启动位置的自动识别,识别为Nor FLASH启动后,函数会自动跳转到NOR_BOOT函数中,识别为Nand FLASH启动后,函数则会跳转到NAND_BOOT函数中,实现语句如下:
_TEXT_BASE存放的是编译连接时U-Boot在SRAM中运行的基地址,由config.mk定义。函数首先检测U-Boot是否在内存中运行,然后检测寄存器rBWSCON[2:1]的值是否为0,为0时跳转到NAND_BOOT函数,不为0时跳转到NOR_BOOT函数。
调用NOR_BOOT时,由于Nor FLASH的结构特点,其支持片内运行可以直接从Nor FLASH中复制U-Boot到SRAM中,所以启动函数较为简单。实现语句如下:
_armboot_start为代码段的起始地址,_bss_start为代码段的末地址,两者间的差值即为U-boot的大小,存放寄存器R2中,寄存器R0中存放的是当前代码的地址。调用一个拷贝循环将U-Boot拷贝到RAM中。
调用NAND_BOOT时,Nand FLASH的读写需要用专门的驱动程序来控制,而驱动程序又比较复杂,采用C语言实现NAND_BOOT的核心功能要比汇编语言容易些[7]。NAND_BOOT函数中复制U-Boot到RAM这段核心代码用C语言实现,其他部分汇编语言实现。鉴于篇幅有限本文只重点说明下U-Boot复制到RAM的实现过程。部分实现语句如下:
NAND_BOOT中调用的C语言函数为int nand_read_ll(unsigned char*buf, unsigned long start_addr, int size), 这个函数在nand_read.c这个文件中。nand_read_ll函数的参数分别存放在R0,R1,R2这3个寄存器中,R0中存放目标地址,R1中存放源地址,R2中存放复制长度[5]。函数的执行过程如图2所示。
图2 nand_read_ll函数执行流程Fig.2 Execution flow chart of function nand_read_ll
U-Boot最终要完成的任务是引导并成功启动内核,内核启动前需要正确设置内核的启动参数,U-Boot与内核的交互式单向的,U-Boot需要将各类参数传递给内核。笔者引导的Linux内核为2.6.32.2,通过对Linux中Nand FLASH分区表分析,可以得出如下的启动参数:
采用NFS方式挂载根文件系统。
UvBoot默认支持uImage格式的linux内核引导,而Linux2.6.32.2默认编译生成zImage格式的linux内核[6],利用mkimage这个 U-Boot自带的工具处理下即可,uImage比zImage多一个大小为0x40的头信息,里面包含CPU体系结构,操作系统,加载到内存的位置,在内存中入口点的位置及映像名等信息。
将编译生成的u-boot.bin通过JTAG下载到FLASH的0地址处,通过tftp将uImage下载到Nand FLASH中,重启开发板,若从串口打印出如下信息则说明U-Boot成功引导了linux内核。
图3 串口打印信息Fig.3 Printed information of UART
BootLoader一直是嵌入式开发的热门研究方向,U-Boot是一款十分优秀的开源BootLoader,对U-Boot的研究具有很大的实用价值。本文对于基于S3C2440硬件平台的自动识别启动原理进行了详细的分析,并提出了实现方案,同时对linux内核的启动参数做了简要分析。编译生成的u-boot.bin烧写到Nor FLASH或者Nand FLASH均能稳定运行,这对进一步嵌入式系统开 发带来了很大的便利。
[1]文全刚.ARM嵌入式技术原理与应用[M].北京:北京航空航天大学出版社,2011.
[2]查婧.ARM9嵌入式系统设计及U-boot移植[D].西安:中科院西安光学精密机械研究所,2009.
[3]韦东山.嵌入式Linux应用开发完全手册[M].北京:人民邮电出版社,2008.
[4]Samsung Electronics.S3C2440A 32-BIT CMOS MICROCONTROLLER USR`MANUAL[S].2004:195-197.
[5]杨水清.ARM嵌入式Linux系统开发技术详解[M].北京:电子工业出版社,2008.
[6]高文辉,师奕兵,张伟.基于S3C2440的U-Boot双启动实现[J].测控技术,2012,31(2):87-91.
GAO Wen-hui,SHI Yi-bing,ZHANG Wei.Implementation of double boot in U-Boot based on S3C2440[J].Measurement&Control Technology,2012,31(2):87-91.
[7]吴玉香,周建香,郭建勋.U-Boot在S3C2410上的移植于功能扩展[J].计算机科学与工程,2010,31(4):729-732.
WU Yu-xiang,ZHOU Jian-xiang,GUO Jian-xun.Porting and expansionofU-BootbasedonS3C2410[J].ComputerEngineering and Design,2010,31(4):729-732.