引言
ARM处理器在嵌入式领域有着非常广泛的应用,特别是ARMv7架构的处理器CortexA8的推出,给消费电子和低功耗移动产品带来重大变革。Windows Embedded Compact 7是微软公司面向嵌入式、移动计算平台推出的新一代操作系统,它是一个开放的、可升级的32位嵌入式操作系统。其特点是与PC平台操作系统Windows高度一致,高度模块化,与平台无关,具有强大实时能力。微软把这些高度模块化,与平台无关功能的代码抽象出来,形成通用的标准函数库,开发人员无需修改这类代码,就可直接调用;与硬件平台密切相关,介于硬件与操作系统之间的BSP程序包(Board Support Package)的部分代码需要开发人员修改与实现。
1 Windows Embedded Compact 7 BSP
1.1 Windows Embedded Compact 7 BSP的组成
图1为Windows Embedded Compact 7(下文简称WinCE7.0) BSP的基本结构,它由4部分组成:
① 硬件抽象层,即OEM适配层。主要实现硬件抽象和启动内核的功能。
② 设备驱动程序。包含特定开发板上的所有硬件驱动程序。
③ 配置文件。一些包含配置信息的文件,它的作用是告诉WinCE7.0的构建系统如何添加组件到内核文件中。
④ 引导程序Bootloader。它是BSP的重要组成部分,是系统加电后运行的第一段程序代码,是BSP开发的第一步,也是关键的一步。Bootloader的主要作用是把WinCE7.0操作系统镜像下载到内存,然后跳转到操作系统内核,启动内核。Bootloader可以通过不同方式下载系统镜像到内存,在WinCE7.0中应用最广泛的是Eboot(Ethernet Bootloader),即通过以太网下载系统镜像。但通过以太网下载系统镜像会有诸多不足之处,比如硬件连接上比较繁琐、下载速度慢等。本文以简单、快捷、实用为理念,介绍如何通过SD卡下载系统镜像到内存,即EbootSD。
1.2 Bootloader的基本结构
Bootloader的代码从用户的角度上可以分为三大类:第一类是微软提供的公共代码库和一些通用的底层驱动程序,用户不必修改;第二类需要用户实现代码,这类代码是为公共代码库的框架调用;第三类代码用户只需要根据硬件平台作简单修改即可。Bootloader从结构上可以分为以下几部分,其框架如图2所示。
图1 BSP结构图2 Bootloader BSP框架
① BLCOMMON库实现了通用的Bootloader框架。它由WinCE的公共代码提供,用户不需要修改,它调用OEM代码完成硬件平台初始化操作,下载操作系统内核镜像到内存。
② OEM代码是需要用户自己实现的代码,它的函数都以OEM开头。
③ Eboot库实现了DHCP、TFTP和UDP等网络协议。
④ BootPart库实现了大部分硬件分区驱动,OEM代码可以调用其中的函数来完成对存储设备的分区。它由WinCE提供,用户不需要修改。
⑤ 存储驱动位于分区驱动之下,实现存储设备的块设备驱动程序,本文介绍的是使用SD卡来实现系统内核镜像下载,所以要实现与SD卡相关的驱动程序。
⑥ 网络驱动程序实现了以太网控制器的驱动。
2 WinCE7.0 Bootloader设计与实现
2.1 Bootloader引导过程
在WinCE7.0系统启动过程中,系统上电后,执行的第一个Bootloader中的函数是StartUp()函数,这个函数与CPU硬件密切相关,它一般是用汇编语言编写的,位于StartUp.s文件中。StartUp()函数主要完成CPU硬件平台的初始化,如关闭CPU的所有中断,关闭内存管理,关闭Cache缓存,初始化时钟,为C语言运行建立堆栈环境,设置并打开MMU进行物理和逻辑地址映射,并打开Cache,把Bootloadert的代码复制到RAM中,然后跳转到内存RAM中执行代码,再由它跳转到C语言代码中的main函数中。在main函数中调用BLCOMMON代码库中的BootloaderMain 函数实现Bootloader引导下载系统镜像,它的控制流程如图3所示。
图3 BootloaderMain函数控制流程
去除该函数中调用返回检测环节,简化后的BootloaderMain()函数如下,在实际中若其中有一个函数调用返回失败,则整个执行将会停止,导致Bootloader程序引导失败,函数调用过程略——编者注。
void BootloaderMain (void){
DWORD dwAction;
DWORD dwpToc = 0;
DWORD dwImageStart = 0, dwImageLength = 0, dwLaunchAddr = 0;
BOOL bDownloaded = FALSE;
KernelRelocate (pTOC) ;//全局变量重新定位
OEMDebugInit ());//初始化串口调试,可以使用OEM Write Debuy String
OEMPlatformInit ();//
OEMPreDownload ();//调用OEM函数,为下载作相关准备
DownloadImage (&dwImageStart, &dwImageLength, &dwLaunchAddr) ;{// Check for pTOC signature ("CECE") here, after image in place
OEMMapMemAddr (dwImageStart, dwImageStart + ROM_SIGNATURE_OFFSET);//检查PTOC结构体的数字签名
OEMLaunch (dwImageStart, dwImageLength, dwLaunchAddr, (const ROMHDR *)dwpToc);//调用Lanch函数,不再返回
}
由上述代码可知,BootloaderMain()依次调用OEM函数来完成硬件系统初始化和下载系统镜像,然后跳转到WinC7.0的内核,并启动内核。下面分析该函数各个阶段所调用的函数。
(1) KernelRelocate()
BootloaderMain()函数调用的第一个函数就是KernelRelocate(),这个函数的功能是把Bootloader中的全局变量重新定位到RAM中去,其代码如下:
static BOOL KernelRelocate (ROMHDR *const pTOC){
ULONG loop;
COPYentry *cptr;
if (pTOC == (ROMHDR *const) -1){
return (FALSE); //如果PTOC指针无效,则进入死循环,无法跳出
}//如果程序运行到这里说明数据段是正确的,从这以后不再读取全局变量
for (loop = 0; loop < pTOC->ulCopyEntries; loop++){
cptr = (COPYentry *)(pTOC->ulCopyOffset + loop*sizeof(COPYentry));
if (cptr->ulCopyLen)
memcpy((LPVOID)cptr->ulDest,(LPVOID)cptr->ulSource,cptr->ulCopyLen);
if (cptr->ulCopyLen < cptr->ulDestLen)
memset((LPVOID)(cptr->ulDest+cptr->ulCopyLen),0,cptr->ulDestLen-cptr->ulCopyLen);
}
return (TRUE);
}
其中,ROMHDR是一个结构体变量,它是一个描述ROM区信息的结构体。在函数KernelRelocate()中,ROMHDR结构体指针pTOC的内容是通过Romimage工具根据配置文件自动填充的,所以它在代码中初始化时被赋值为-1,通过pTOC可以把全局变量重新定位到内存RAM中,以便引导程序使用。
(2) OEMDebugInit()
OEMDebugInit()函数使能系统UART串口调试功能。在用户移植开发的过程中,程序调试是必不可少的,WinCE7.0可以通过PC的串口工具连接硬件开发板,以实现对程序的调试跟踪。在AM3359处理器中使能UART功能是非常简单的,通过使能相应的寄存器,配置相应的波特率、数据位、停止位、校验位就可以使用串口功能。
(3) OEMPlatformInit()
这个函数完成的是CPU的初始化,时钟设置,系统I/O初始化,I2C总线初始化等。
(4) OEMPreDownload()
OEMPreDownload()函数在下载镜像之前被调用。从流程上来说是为下载内核做一些准备工作,该函数执行后,根据返回值的不同可以选择不同方式下载内核,或者跳转执行。最典型的是通过调试串口向用户输出菜单选项,然后接收用户的相应输入。本设计是通过SD卡下载系统镜像文件,所以需要设置AM3359的启动状态控制寄存器的值,启动SD卡,然后设置g_bootCfgS结构体的boot设备,调用BLSDCardDownload()初始化SD卡,初始化FAT文件系统为下载系统镜像做准备,部分代码如下:
UINT32 *pStatusControlAddr = OALPAtoUA(AM33X_DEVICE_BOOT_REGS_PA);
dwSysBootCfg = INREG32(pStatusControlAddr);
OUTREG32(pStatusControlAddr, dwSysBootCfg|0x17);
g_bootCfg.bootDevLoc.LogicalLoc = AM33X_MMCHS0_REGS_PA;:
g_bootCfg.kitlDevLoc.LogicalLoc = AM33X_UART0_REGS_PA;
g_eboot.bootDeviceType=BOOT_SDCARD_TYPE;
rc = BLSDCardDownload(g_bootCfg.filename);
其中,BLSDCardDownload通过fileio_operations_t类型的结构体fileio_ops实现对SD卡中文件的读写控制,fileio_operations_t结构如下:
typedef struct fileio_operations_t {
int (*init)(void *drive_info);
int (*identify)(void *drive_info, void *Sector);
int (*read_sector)(void *drive_info, UINT32 LogicalSector, void *pSector);
int (*read_multi_sectors)(void *drive_info, UINT32 LogicalSector, void *pBuffer, UINT16 numSectors);
void *drive_info;
} S_FILEIO_OPERATIONS, *S_FILEIO_OPERATIONS_PTR;
在BLSDCardDownload函数中,把SD卡相关操作的函数指针初始化fileio_ops如下:
fileio_ops.init = &SDCardInit;
fileio_ops.identify = &SDCardIdentify;
fileio_ops.read_sector = &SDCardReadSector;
fileio_ops.read_multi_sectors = &SDCardReadMultiSectors;
这样通过把与SD卡文件操作相关的函数指针初始化fileio_ops后,BLSDCardDownload()就可以实现对SD卡的文件读写操作。其中,SDCardInit()函数的关键是初始化SD卡控制器,设计中硬件电路连接如图4所示。
图4 SD卡硬件连接
在最初始使用SD卡之前,先通过处理器AM3359的MMC端口连线向SD卡发送CMD1、CMD55、CMD41命令字来检测硬件类型及连接情况,其控制流程如图5所示。
图5 SD卡初始化检测
2.2 SD卡FAT16文件系统
FAT16文件系统把存储卡划分为几个区,包括DBR扇区、保留扇区、FAT表1、FAT表2、根目录和数据区,具体结构如图6所示。
图6 FAT16文件系统结构
DBR区(DOS BOOT RECORD)即操作系统引导记录区,也称为引导扇区或启动扇区。FAT16将存储空间按一定数目的扇区为单位进行划分,它记录了SD存储卡的所有重要信息,一共512个字节,其结构如图7所示。
BPB(BIOS Parameter Block)参数块记录着本分区的起始扇区、结束扇区、文件存储格式、介质描述符、根目录大小、FAT个数、分配单元(也称簇)的大小等重要参数,把SD卡中的系统镜像文件下载到RAM时,就需要通过此块数据获取SD存储器的基本信息。
FAT表实际上是一个数据表,用来记录数据区簇链结构。FAT表记录提文件或文件夹数据所在簇号的指针链表,不存储文件数据,通过它可以访问一个文件或文件夹的完整数据。
根目录扇区,FAT文件系统的目录结构是一棵有向的从根到叶的树,通过根目录中记录的文件的首簇号在FAT表中查找相应的簇号,就可以准确无误地读取整个文件数据。
图7 DBR结构
在FAT格式化的SD卡中读取系统镜像文件,首先通过文件系统获取SD卡储存器的引导扇区中的BPB参数块记录,获取SD卡储存器的基本信息,然后在OEMPreDownload()函数中调用BLSDCardDownload(g_bootCfg.filename);在该函数中调用FleIoOpen(g_bootCfg.filename),遍历根目录,查找“NK.BIN”文件,如果找到,则BLSDCardDownload()函数返回BL_DOWNLOAD标记,准备下载系统镜像。
2.3 下载系统镜像文件
通过WinCE7.0的PB工具编译的镜像文件,一般都会同时以BIN文件和NB0文件两种形式给出。BIN文件是Binary文件简称,也就是Binary Image Data Format(二进制镜像数据格式),它不能直接烧到Flash中运行,而NBO文件则可以直接烧写到Flash中运行。本设计中使用的是BIN文件,下面对BIN文件结构进行分析,其文件存储结构如图8所示。
图8 BIN文件存储结构
BIN文件内容开头是以BIN文件标识开始,其内容为“42 30 30 30 46 46 0A”,然后加空格结尾(00),占用8个字节,其后的8个字节是BIN文件复制到目地的起始地址和文件大小,各占4字节,其后都是系统镜像文件的记录区分块,每块的结构都是一样,每块的大小都是16字节。其存储结构图如图9所示。
图9 块记录存储结构BIN文件下载略——编者注。
通过分析OEMPreDownload()函数,已经为下载系统镜像文件做了相应的准备工作,在DownloadImage()函数中根据系统镜像文件的类型(BL_IMAGE_TYPE_BIN)调用DownloadBin()函数,在DownloadBin()函数中通过OEMReadData()读取系统镜像数据,OEMReadData()中通过bootDeviceType启动设备类型选择函数BLSDCardReadData()。该函数调用FileIoRead()获取NK.BIN文件数据,通过上一小节中对BIN文件结构的分析可知,把NK.BIN文件的原封数据全部下载到RAM中,系统镜像是不能运行的,所以要把BIN文件进行相应的解析,即要把所有块记录的数据进行解析,然后再进行拼接,才能得到整个系统的镜像数据,系统才能运行启动。
当系统镜像已经被下载到RAM时,需要把此时的PC(程序计数器)指向系统镜像的地址,用来启动WinCE7.0内核。BootloaderMain()通过调用一个不返回的函数OEMLaunch()来获取WinCE7.0系统镜像的启动地址,程序由此跳转到WinCE7.0内核,并启动。
3 BIB配置文件
在WinCE7.0 Bootloader中使用的一个重要的配置文件就是BIB(Binary Image Builder File)文件。BIB文件略——编者注。
设计中Ebootsd.bib (\\WINCE700\\platform\\AM33X_BSP\\SRC\\Bootloader\\EBOOT\\SDMEMORY中)文件实际配置代码略——编者注。
4 启动EBootSD
在开发工具PB7.0中,编译生成得到MLO,ebootsd.输出结果表明, EBootSD可以成功引导WinCE7.0系统镜像。
nbo,NK.bin文件,通过TI公司的SD卡格式化工具(TI_SDCard_boot_utility_v1_0),把所得到的文件复制到SD卡中,或者直接把SD卡按FAT16/32格式化后,再把文件复制到SD卡上,把SD卡插入Beaglebone开发板中,通过串口连接PC串口,上电启动,在串口工具中看到的输出内容如图10所示。
图10 SD卡启动串口输出
结语
本文设计了基于SD卡存储器的以CortexA8为内核的AM3359处理器的WinCE7.0引导程序EbootSD。实践表明,它达到了设计目标,为BeagleBone的WinCE7.0的BSP的移植工作提供了很大的便利。