引言
嵌入式系统启动的第一步就是运行一段引导程序,其作用是进行初始化,如改变系统时钟、关闭WatchDog、初始化存储控制器、将更多的代码复制到内存中等。如果它能将操作系统内核复制到内存中运行,就称这段程序为Bootloader。其一般固化在ROM、Flash等非易失性存储器上。Bootloader的设计依赖于CPU的体系结构和具体的嵌入式板级配置,即使CPU相同,外设也可能不同,所以这种强硬件依赖性决定了开发通用的Bootloader相当困难。而Bootloader的开发又是整个嵌入式系统开发的第一步,非常关键。本文从可利用资源和实际的产品开发周期角度考虑,移植已有的Bootloader源码符合大多数项目的开发要求。目前,支持ARM架构的有UBoot、Vivi、RedBoot等。
本文首先分析了UBoot的主要功能及结构特点。然后基于工控级的AT91RM9200芯片,详细地介绍了UBoot1.1.6版本的移植过程,并在此基础上设计了菜单式的UBoot,为后续的开发提供了很大的便利。最后,通过改进的UBoot成功地在目标板上启动了Linux 2.6.22内核。
1UBoot简介
UBoot(Universal Bootloader,通用Bootloader)是一款开放源代码、功能强大的Bootloader,遵循GPL条款的开放源代码项目。 “通用”有两层含义:可以引导多种操作系统和支持多种架构的CPU。 它支持Linux、 NetBSD、VxWorks、QNX、RTEMS、ARTOS、LynxOS等操作系统,以及PowerPC、MIPS、x86、ARM、Nios、XScale等架构的CPU。UBoot的源码目录、编译形式与Linux内核相似,具有较高的可靠性和稳定性,功能设置灵活,调试方便,有丰富的设备驱动源码和开发调试文档。
2UBoot启动流程和源码结构
2.1UBoot启动流程
UBoot的启动分为两个阶段: 第一阶段使用汇编语言来实现,它完成一些依赖于CPU体系结构的初始化,并调用第二阶段的代码;第二阶段则通常使用C语言来实现,这样可以实现更复杂的功能,而且代码会有更好的可读性和可移植性。
UBoot第一阶段的功能:
◆ 初始化硬件设备,包括关闭WatchDog、关中断、设置CPU的速度和时钟频率、初始化RAM等;
◆ 为加载UBoot第二阶段代码准备RAM空间;
◆ 复制UBoot第二阶段代码到RAM空间;
◆ 设置好堆栈;
◆ 跳转到第二阶段代码的C入口点。
UBoot第二阶段的功能:
◆ 初始化本阶段要使用到的硬件设备;
◆ 检测系统内存映射;
◆ 将内核映像和根文件系统映像从Flash上读到RAM空间中;
◆ 为内核设置启动参数;
◆ 调用内核。
为了方便开发,至少要初始化一个串口以便程序员与UBoot进行交互。对于检测内存映射,由于UBoot是根据各类开发板编写的,所以可以根据目标板的情况直接设置。UBoot将压缩的内核映像和文件系统复制到RAM中,解压后直接跳到内核的入口点即可调用内核。
2.2UBoot源码结构
UBoot根目录下共有26个子目录,可分为4类:平台相关的或开发板相关的,通用函数,通用的设备驱动程序以及UBoot工具、示例程序、文档。其底层目录的层次结构如图1所示。
图1Boot底层目录的层次结构
从图中可以看出,board、cpu、lib_xxx等目录最接近底层硬件,所以移植过程中修改的重点也是这几个目录中的文件。
3UBoot在AT91RM9200上的移植
3.1AT91RM9200的启动方式
AT91RM9200分为片内引导和片外引导。片内引导通常采用串口下载并引导UBoot,并将程序烧写到Flash上,然后就可以通过跳线的方式从片外引导执行已经烧写到片外Flash上的UBoot。
裸板只能用片内引导方式。载入一个12 KB以内的小程序loader.bin到内部SRAM运行,当这个小程序初始化SDRAM后,再把uboot.bin下载到SDRAM的高端运行(UBoot大于12 KB,所以不能直接下载)。PC机跳到SDRAM的UBoot位置运行,UBoot启动后再用UBoot命令把boot.bin及uboot.gz下载到SDRAM的低端,再用Flash烧写命令烧写进Flash,就可以片外启动了。AT91RM9200片外启动的起始地址就是0x0,通常此地址存放的是boot.bin,由其将uboot.gz解压到高端RAM中,再运行真正的uboot.bin,就真正启动UBoot了。loader.bin和boot.bin是较简单的开放源码,可以下载直接使用,无需改动。
3.2与目标板相关的源码文件
与目标板相关的源码文件包括如下内容:
① 在cpu/at91rm9200/目录下分别为cpu.c、interrupts.c和serial.c等文件。这些文件都是与CPU相关的,与目标板本身的配置无关。移植时选择相同CPU下的文件即可,无需修改。
② 在board/at91rm9200dk/目录下分别为flash.c、at91rm9200dk.c、config.mk、Makefile和uboot.lds。其中flash.c是UBoot读、写和删除Flash设备的源代码文件,是移植的重点,必须根据目标板使用的Flash类型来修改。at91rm9200dk.c是板级初始化、地址初始化等与目标板相关设置的文件,也需要修改。
③ 在include/目录下的flash.h文件。根据flash.c文件也要做相应的修改。
④ 在include/configs/目录下的at91rm9200dk.h文件。该头文件包括目标板各项参数的宏定义以及UBoot与内核传递参数的定义,需要修改。
3.3移植的具体过程
移植的具体过程如下:
① 在board 目录下选择相近的板型。本文是基于工控级的AT91RM9200芯片,所以直接选择at91rm9200dk板型进行修改。
首先修改该文件夹下的flash.c文件。目标板使用的Flash芯片是Intel公司的28F128J3,源代码的Flash型号是AT49BV16,所以要自己设计flash.c代码。参考Intel公司相近型号的Flash文件以及芯片手册,代码中首先宏定义设置Flash的实际大小以及块大小。
#define FLASH_BANK_SIZE 0x1000000
#define MAIN_SECT_SIZE 0x20000
然后实现flash_init()、flash_print_info()、flash_erase()、write_word()、write_buff()等函数,完成Flash的初始化、信息输出、Flash擦除、Flash读写,以及从RAM到Flash的复制等功能。其中注意将Flash的位宽定义成16位。
#define FLASH_PORT_WIDTH 16//定义位宽为16位
修改了flash.c后,就要在include/flash.h 文件中添加相应Flash的ID号:
#define MICRON_ID_28F128J3 0x00180018
接着修改at91rm9200dk.c文件,改动50行左右的机器类型码,将
gd>bd>bi_arch_number=MACH_TYPE_AT91RM9200
改为:
gd>bd>bi_arch_number=MACH_TYPE_AT91RM9200DK
因为内核使用的版本是Linux 2.6.22,要使用262的机器类型码。否则引导内核时会出现unrecognized/unsupported machine ID的错误而不能启动。
② 进入include/configs 目录,修改at91rm9200dk.h头文件。主要是修改关于Flash的一些参数,如Flash的实际大小和起始地址。然后添加UBoot传递给内核的命令行参数:
#define COMMAND_LINE "initrd=0x21100000,0x4000000 root=/dev/ram0 init=/linuxrc console=ttyS0,115200 mem=32M rw"
#define CONFIG_BOOTARGS "initrd=0x21100000,0x4000000 root=/dev/ram0 init=/linuxrc console=ttyS0,115200 mem=32M rw"
由于采用的是较高版本的UBoot,会出现启动后显示downloaded successfully,然后进入死循环的情况。解决方法是在该文件中加入宏定义:
#define CONFIG_SKIP_ LOWLEVEL_INIT
因为目标板会自动进行初始化,所以要使用此宏定义跳过LOWLEVEL等级的初始化,否则会成为死循环。
③ 修改完相关文件后,使用命令编译镜像文件。
#make at91rm9200dk_config
#make
如果编译使用的交叉编译器不支持软件浮点,则会出现错误: uses hardware FP,where as uboot uses software FP。解决方式是:修改cpu/[CPUTYPES]/config.mk,删除msoftfloat,先执行make distclean,然后配置,执行make。
编译完成后,生成镜像文件uboot.bin,然后制成压缩包uboot.bin.gz,就可以烧写到目标板Flash中完成移植过程了。
4设计菜单式UBoot
为了方便后续的开发工作,设计了菜单式的UBoot,将大量的命令行代码设计成一键选择的菜单式,大大节省了开发时间。下面详细介绍设计过程。
(1) 添加UBoot的命令格式
要设计菜单式UBoot,就要添加新的UBoot命令,其命令格式定义为:
U_BOOT_CMD(name,maxargs,rep,cmd,usage,help);
其中,name为命令名,非字符串,用#符号转换为字符串;maxargs为命令的最大参数个数;rep为是否自动重复;cmd为该命令对应的响应函数;usage为简短的使用说明;help为较详细的使用说明。
(2) 定义菜单命令
首先在common 目录下新建cmd_menu.c 文件,其中定义菜单命令的代码如下:
U_BOOT_CMD(
menu,3,0,do_menu,
"menudisplay a menun",
"display a menu,to select it to do somethingn"
);
(3) 定义菜单命令响应函数
定义do_menu()函数,该函数调用定义的子函数display_menu()、putkey()、menu_shell()。它们的功能分别是:显示menu主菜单各选项,等待用户输入选择,以及对用户的输入执行相应的命令行。
(4) 编译菜单命令
编写完cmd_menu.c文件后,在common/Makefile 文件中加入目标代码cmd_menu.o。在include/configs/at91rm9200dk.h 文件中加入:
#define CONFIG_BOOT_MENU 1
然后重新编译、下载UBoot镜像文件,就可以使用菜单命令了。菜单式UBoot如图2所示,可方便地进行操作。
图2菜单式UBoot
结语
本文根据工控级目标板的硬件配置,以UBoot中配置相似的标准板型为参考,修改与硬件相关的源代码以适应具体应用需要。通过移植UBoot,避免了在嵌入式系统开发过程中有新的应用需求而完全重写Bootloader的局面,缩短了整个系统的开发周期。同时为了方便后续的开发,笔者在原版本UBoot的基础上加入了菜单功能,一键选择的功能为开发者和使用者都提供了便利。文中还提到了移植遇到的问题及解决方法,为相关开发人员遇到同类问题提供参考。
目前,移植的UBoot在目标板上稳定运行,菜单功能使用良好,引导Linux2.6内核启动正常,希望本文对相关工作的开发人员有所帮助。