嵌入式系统是以应用为中心,以计算机技术为基础,软硬件可裁减、适应应用系统对功能、可靠性、成本、体积、功耗等有严格要求的专用计算机系统。嵌入式系统是一个层次性结构,从底层到应用分别是:硬件、启动引导程序、嵌入式操作系统(Linux)、文件系统、应用系统。系统上电或硬件复位后,CPU执行的第一段代码就是启动引导程序,启动引导程序在嵌入式Linux系统的设计与实现的过程中起着重要的作用。
1、Bootloader的工作原理与功能
1.1 工作原理
Bootloader是系统内核运行之前运行的代码,BootLoader完成内核加载启动。嵌入式系统开发过程,常涉及底层BOOT LOADER移植,操作系统引导、应用程序运行都与Bootloader有关,所以说Bootloader移植是嵌入式系统开发基础。不同CPU体系结构对应不同Bootloader,另外Bootloader的实现也依赖于具体板级硬件。
嵌入式开发首先根据目标板特性开发Bootloader,依次完成下面任务:初始化底层硬件(CPU、存储器);设置堆栈、Cache;代码重定向;加载内核、文件系统映像;设置内核启动参数;启动内核,控制权转交给操作系统。
1.2 Bootloader主要功能
设计一个Bootloader,须具备以下基本功能:
l 正确初始化硬件,使系统平台正常启动。
l 提供映像文件加载接口,通过串口、网口下载映像到Flash,为内核启动作准备。
l 加载Linux内核到RAM中,启动内核和文件系统,并进一步启动应用程序。
而完备的Bootloader,还可以扩展测试功能:
l 开发板设计开发调试过程,Bootloader通过交互界面与系统平台交互,测试平台器件。
l 开发阶段,可以用于调试内核。系统内核能正常启动是Bootloader的最终目标。
l 提供串口交互的命令控制台。支持配置功能:设置IP,MAC,系统时间等。
Bootloader有两种操作模式:启动加载模式和下载模式;启动加载模式是开发板发布后提供的稳定工作模式;下载模式一般是在开发阶段的工作模式,提供串口交互界面。
2、PXA272硬件平台
Pxa272是最新推出的32位XScale RISC核心微处理器,兼容ARMv5TE指令;工作频率可达520MHz;采用7级超流水线结构;支持Intel Wireless MMXTM指令集增强多媒体处理功能;动态电源管理实现低功耗;提供32KB D/I Cache;集成64M Flash和265K SRAM;
硬件平台是基于Pxa272微处理器的自研发的开发板Uranium V0.90。硬件结构中的主要器件:AC97、SDRAM、TFT LCD、触摸屏、网络LAN、PCMCIA插槽等外设。
3、Bootloader映像文件结构设计
Bootloader结构由链接脚本决定,链接脚本用于控制目标文件格式。决定如何将各目标文件合并起来并安排数据和符号的位置。通常在编译的最后一步完成。脚本主要目的描述如何将输入文件的各段映射到输出文件中,控制目标文件的布局。脚本文件Uranium.lds的结构:
OUTPUT_ARCH(arm)//指定ARM目标板体系结构
ENTRY(_start) //_start为程序入口
SECTIONS { . = 0x00000000;
__boot_start = .; //汇编中指定的boot起址
.start ALIGN(4) : { *(.text.start)} //文件text段
.text ALIGN(4) : { *(.text) }
.rodata ALIGN(4) : {*(.rodata)} //只读data段
.data ALIGN(4) : {*(.data) } //可读写data段
.got ALIGN(4) : {*(.got) } //全局偏移量表
__boot_end = .; //bootloader终址
.bss ALIGN(16) :{*(.bss) } //未初始化data段
……}
ARM体系结构微处理器在复位后PC指向0x00000000,PXA272片载Flash连接在nCS0上,将Flash映射到0x00000000作为启动存储器。
4、Bootloader的实现与扩展
启动引导程序分成硬件初始化(Boot)和内核加载两个部分(Loader);Boot部分与处理器体系结构紧密相关,通常用汇编语言实现;Loader部分在完成硬件初始化后,将加载映像文件并启动内核,这部分一般用C语言实现。
4.1硬件初始化(Boot)
可执行Boot映像必须且只有一个全局入口。在汇编文件Start.S中指定入口:
_start : b start
Bootloader由多个文件组成。首先是汇编语言实现的Start.S,完成底层硬件初始化:
start: bl define_gpio //设置gpio
bl enable_cpu //设置CPU时钟
bl setup_memory //初始化静动态存储器
bl relocate_boot //复制boot代码
bl setup_stack //建立堆栈
bl clear_bss //清除bss段
ldr pc, =main //跳转到C代码
切换CPU工作模式、屏蔽中断。启动引导程序不需要处理中断,所以要屏蔽所有中断。
设置CPU:PXA272提供动态时钟和电源管理,可以根据应用负荷,选择CPU的工作模式;PXA272电源管理提供四种工作模式:Turbo模式、运行模式、空闲模式、睡眠模式。
建立堆栈和清除数据区:堆栈、DATA、BSS段初始化是运行C代码前必要条件。C语言函数编译必须提供堆栈存放临时变量、调用位置、寄存器信息。除堆栈外,需DATA段和BSS段存放C代码中有初始值的全局或静态变量以及没有指定初始值的全局变量。
系统存储器初始化:通过配置功能寄存器来实现;在Pxa272上电后,SDRAM处于睡眠无时钟状态,初始化必须完成SDRAM状态机转化过程,相关的寄存器:MDREFR、MDCNFG等。SDRAM的状态转换通过配置存储器的相应位,产生对应的命令实现状态机转换[1]。
4.2内核加载(Loader)
为加载内核,需初始化必要设备:网络接口、串行接口、Flash等;PXA272支持BTUART(蓝牙)、FFUART(全功能串口)、 STUART(标准串口)。可以通过串口实现与终端用户的交互,终端打印信息是非常重要的调试手段。片载的Flash遵循CFI规范(Common Flash Interface)。
内核加载前规划好内存空间:设置内存大小和起始地址,BootLoader、内核、ramdisk、root镜像在flash和SDRAM中的存放区域、大小。在这里确定好下载各个镜像文件存放的地址以及镜像加载后释放的位置。内存地址的安排关系到操作系统能否正常的启动、运行。
Parts[i].name = "XXXX"; //镜像名(Boot/Kernal/Ramdisk)
Parts[i].sramb = XXXX_SRAM_BASE;//在flash中的基地址
Parts[i].srams = XXXX_MAX_SIZE; //在flash中镜像大小
Parts[i].dramb = XXXX_DRAM_BASE; //在DRAM中的基地址
Parts[i].drams = 0;
Parts[i].maxs = XXXX_MAX_SIZE; //镜像大小
这里基本确定flash和DRAM的地址映射。
加载内核、文件系统映像,设置启动参数:读取映像大小和格式,将映像从Flash定向到RAM。定向后,就可启动 Linux 内核。启动内核前,要作些准备工作:设置启动参数。Linux 2.4.x 以后的内核以标记列表(tagged list)形式传递启动参数。由Boot Loader设置常见启动参数:ATAG_CORE、ATAG_MEM、ATAG_CMDLINE、ATAG_RAMDISK、ATAG_INITRD 等。启动参数列表以标记 ATAG_CORE 开始,以标记 ATAG_NONE 结束。
……
tags = (struct tag *)BOOT_PARAMS;
tags->hdr.tag = ATAG_CORE; //以CORE开始
tags->hdr.size = tag_size(tag_core);
……
tags = tag_next(tags);
tags->hdr.tag = ATAG_NONE; //以NONE结束
tags->hdr.size = 0;
……
Boot Loader 调用 Linux 内核的方法是直接跳转到内核的第一条指令处:
theKernel = (void *)mp->dramb;//Linux内核基址
theKernel (0, ARCH_URANIUM, 0xa0008000);
至此,Bootloader完成该作的工作,跳转到内核入口点,内核接管控制权,开始启动Linux。
4.3 启动程序的扩展
在开发板的硬件测试阶段,扩展命令接口,以协助硬件测试工作。
1.内存测试(MEM_TEST)[5]
测试存储器有3个部分:数据总线测试、地址总线测试和器件测试;优化通用的测试程序,能测试出错误,还对错误进行分析诊断,给出错误原因,并定位错误。
数据线测试(走1法):选择有代表性的写入值(0x01(单位),0x03(两个相邻位), 0x07(三个相连位),0x0F(四个相连位))。分别将值写入内存地址,写入后立刻读出与写入前的值比较,相等则将写入值逻辑左移一位作为下一次写入值。如此。直到所有值被测试完。如有不等,则报错将写入前后的值显示出来。
地址线测试:取测试值0xaaaaaaaa,选择测试地址,分别写入测试值,比较写入前后值。然后取其反值为测试值写入测试地址。改换测试地址,按从低向高的顺序依次写入1的递增值,比较写入前后的值;再按上一步的地址顺序写入1递增值反值,比较写入前后值;在每步写入前后值的比较中如果不一致,就输出出错的地址和写入前后的值。
器件测试:用来测试器件完成性,确定器件每一位正确。选择一组随地址变化的值写入内存,再读出比较。然后取这些值的反写入内存验证。
如测试内存代码放在SDRAM上,测试时会修改内存,这样会造成系统崩溃。所以测试内存时,把代码放在PXA272片载SRAM上。
2.液晶屏测试(LCD_TEST):启动引导程序没用到LCD,为确定目标板的LCD正常工作,提供LCD_TEST命令测试LCD。点亮LCD前,初始化 LCD控制器寄存器,划出一块内存作为LCD显示缓冲区。通过参数来控制写入显示缓冲区中的数据以显示不同图形。
3.触摸屏测试(TS_TEST):ADS7843触摸屏芯片具备12位A/D转换和低导通电阻模拟开关,通过控制GPIO模拟ADS7843传输时序,将控制字送入ADS7843,再串行读取坐标值。最后,将读取的x和y坐标送串口显示。
除上述器件测试命令,还有SPI、RTC等命令。扩展命令对于处于硬件调试阶段的开发板非常重要,可以方便调试工作。同时也能体现Bootloader在嵌入式系统开发中的地位。
结束语
本文总结了设计和实现基于PXA272处理器开发板的Bootloader工作,对其功能进行扩展,扩展了一些硬件测试接口以方便硬件测试。在开发过程中,体会到要编写开发板的Bootloader,需要详细了解目标板平台结构、器件工作原理以及微处理器的体系结构,先从实现基本功能入手,再不断扩展。
本文作者创新点:对基于Pxa272的嵌入式系统的Bootloader的实现作了详细分析;对关键步骤SDRAM初始化工作详细说明;并将Bootloader的功能扩展应用于硬件平台的测试工作,给硬件工程师调试硬件提供一个良好的软件平台。
参考文献
[1]Intel PXA27X Processor Family Developer’s Manual[Z].Intel. October 2004
[2]Common Flash Memory Interface Specification Release2.0[Z].AMD. Dec 2001
[3]刘军芳,李众立.基于s3c2410开发板的Boot Loader的启动分析[J].微计算机信息,2006,6-2:201-203
[4]杜春雷.ARM 体系结构与编程[M].北京:清华大学出版社,
[5]马学文.嵌入式系统中Bootloader的设计和实现[J].计算机工程,2005,31-7:96-97
[6]张大波.嵌入式系统原理、设计与应用[M].北京:机械工业出版社.2005.01