您的位置:首页 > 其它

启动程序BootLoader的分析

2008-01-10 17:13 465 查看
function StorePage(){d=document;t=d.selection?(d.selection.type!='None'?d.selection.createRange().text:''):(d.getSelection?d.getSelection():'');void(keyit=window.open('http://www.365key.com/storeit.aspx?t='+escape(d.title)+'&u='+escape(d.location.href)+'&c='+escape(t),'keyit','scrollbars=no,width=475,height=575,left=75,top=20,status=no,resizable=yes'));keyit.focus();}

启动程序BootLoader的分析
1 什么是BootLoader
BootLoader就是在操作系统内核运行之前运行的一段小程序。通过这段小程序,我们可以初始化硬件设备、建立内存空间的映射图,从而将系统的软硬件环境设置成一个合适的状态,以便为最终调用操作系统内核准备好正确的环境。
BootLoader是严重地依赖于硬件而实现的,特别是在嵌入式系统。因此,在嵌入式系统里建立一个通用的BootLoader几乎是不可能的。尽管如此,我们仍然可以对BootLoader归纳出一些通用的概念来,以指导用户进行特定的BootLoader设计与实现。
嵌入式系统中,Bootloader的意义与作用与PC上的BIOS有点类似,它对开发板上的主要部件如CPU、SDRAM、FLASH、串口等进行了初始化,可以使用Bootloader下载文件到开发板,可以浏览目录,可以烧录flash,可以启动系统等,实际上,一个功能比较强大的Bootloader已经相当于一个微型的操作系统了。
初始化基础硬件——CPU速度、存储器定时、中断、检测ram大小。
引导装载程序通常是在任何硬件上执行的第一段代码。台式机这样的常规系统中,通常将引导装载程序装入主引导记录(Master Boot Record,MBR)中,在嵌入式设备中,通常将引导程序放置在不易丢失的存储器的开始地址或者是系统冷启动时PC寄存器的初始值。通常,在台式机或其他系统上,BIOS将控制移交给引导装载程序。而在嵌入式系统中,通常并没有像BIOS那样的固件程序(注,有的嵌入式CPU也会内嵌一段短小的启动程序),因此整个系统的加载启动任务就完全由BootLoader来完成。引导程序完成自己的任务后,也将控制权移交给操作系统。
总体上Bootloader需要完成以下工作。
n 初始化CPU速度;
n 初始化内存,包括启用内存库,初始化内存配置寄存器等;
n 初始化中断控制器,在系统启动时,关闭中断,关闭看门狗;
n 初始化串行端口(如果在目标上有的话);
n 启用指令/数据高速缓存;
n 设置堆栈指针;
n 设置参数区域并构造参数结构和标记(这是重要的一步,因为内核在标识根设备、页面大小、内存大小以及更多内容时要使用引导参数);
n 执行POST(加电自检)来标识存在的设备并报告有何问题;
n 为电源管理提供挂起/恢复支持;
n 传输操作系统内核镜像文件到目标机。也可以将操作系统内核镜像文件事先存放在Flash中,这样就不需要BootLoader和主机传输操作系统内核镜像文件,这通常是在做成产品的情况下使用。而一般在开发过程中,为了调试内核的方便,不将操作系统内核镜像文件固化在Flash中,这就需要主机和目标机进行文件传输;
n 跳转到内核的开始,在此又分为ROM启动和RAM启动。所谓ROM启动就是用XIP技术直接在Flash中执行操作系统镜像文件;所谓RAM启动就是指把内核镜像从Flash复制到RAM中,然后再将PC指针跳转到RAM中的操作系统启动地址。
嵌入式系统的资源有限,程序通常都是固化在ROM中运行。ROM中程序执行前,需要对系统硬件和软件运行环境进行初始化,这些工作是用汇编语言编写的启动程序完成。
启动程序是嵌入式程序的开头部分,应与应用程序一起固化在ROM中,并首先在系统上运行,它应包含各模块中可能出现的所有段类,并合理安排它们的次序。
2 BootLoader和主机之间文件传输的通信协议
最常见的情况就是,目标机上的BootLoader通过串口与主机之间进行文件传输,传输协议通常是xmodem/ymodem/zmodem协议中的一种。但是,串口传输的速度是有限的,因此通过以太网连接并借助TFTP协议来下载文件是个更好的选择。
此外,在论及这个话题时,主机方所用的软件也要考虑。比如,在通过以太网连接和TFTP协议来下载文件时,主机方必须有一个软件用来的提供TFTP服务。
3 BootLoader选项
程序员可以在自己的BootLoader中实现不同的启动选项,在此将对一些常用的选项进行讨论。如:BootLoader通信方式,串口中的启动功能等。
图9-4是最常见的嵌入式系统目标机和主机通信的模型。开发人员可以使用超级终端通过串口向目标机发送命令,由于串口协议是最简单、可靠的,因此经常被使用在系统未启动前的阶段。



图9-4 嵌入式系统中目标机和主机通信模型

串口命令行选项

超级终端上使用串口协议,用文本形式来显示和目标机的通信过程。
用户可以在超级终端的命令行中指定目标机执行某个命令。这些命令包括
n 从Ethernet下载操作系统镜像文件;
n 从Flash启动(这要求操作系统镜像文件已经被固化在Flash中);
n 启动RAM测试程序;
n 对目标机的I/O端口进行测试;
开发人员可以自己对这些命令进行修改或者扩充。
下面就是通过串口通信来控制目标机的菜单样例:

*************************
Generic BootLoader Version 1.0
*************************
Select option:
D – Download Image
M – Memory Test
B – Start Windows CE .NET
E – Download via Ethernet
P -- Download via Parallel
A – Dial-up Boot
=> (enter your selection here)

下面解释这些命令的功能。
n Memory test命令
这个命令包含了两种测试方式:其一,对设备寄存器的访问测试。由于ARM芯片内部的各个寄存器都是内存编址的,因此对设备寄存器的访问可以等同于对内存的读写;其二,对常规内存进行测试,方法大致是向一个内存地址写一个数据,再读一边,如果值相等,则说明数据总线和地址总线工作正常没有发生短路现象。
在嵌入式系统开发过程中,硬件的制作是第一步。当硬件成型之后,就需要软件来测试硬件平台是否符合设计要求,因此对硬件测试要从RAM、Flash、register等个方面进行。
下面列出的是x86平台下测试寄存器的样例代码:

UCHAR __inline READ_PORT_UCHAR(PUCHAR port)
{
return _inp((USHORT)port);
}
VOID __inline WRITE_PORT_UCHAR(PUCHAR port, UCHAR value)
{
_outp((USHORT)port, (value));
}
For other platforms other than x86, you use
UCHAR __inline READ_PORT_UCHAR(PUCHAR port)
{
return *(volatile unsigned char * const)port;
}
VOID __inline WRITE_PORT_UCHAR(PUCHAR port, UCHAR value)
{
*(volatile unsigned char * const)port = value;
}
下面的代码显示了如何读写普通内存:
ULONG __inline READ_REGISTER_ULONG(PULONG Register)
{
return *(volatile unsigned long * const)Register;
}
VOID __inline WRITE_REGISTER_ULONG(PULONG Register, ULONG
value)
{
*(volatile unsigned long * const)Register = value;
}

n Program FLASH命令
由于硬件结构的原因,Flash的写方式和RAM是不同的,因此对Flash是否能够正常读写一定要编程检测。读Flash的方式基本和读RAM一样,可以随机读写。
下面,我们以Intel StrataFlash Memory(J3)Flash为例,简单说明如何擦写Flash。
Intel StrataFlash Memory(J3)Flash是一款Nor型的Flash。Flash将其内部存储空间分割成一个个block,要对Flash写编程,就应当将block擦除,然后执行写操作。Flash有一个特性,即如果要将某一存储单元从值1改变成值0,只有通过擦除的方式,而不能直接把值0写入目标地址。
根据上述列出的编程介绍,我们用下列函数:

void IntelFlashErase(U16 *MY_FLASHADDR)
{
U16 temp;

*(U16*)MY_FLASHADDR = 0x60;
*(U16*)MY_FLASHADDR = 0xD0;

*(U16*)MY_FLASHADDR = 0x20;
*(U16*)MY_FLASHADDR = 0xD0;
}

void IntelFlashWrite(U16 *MY_FLASHADDR)
{
*(U16*)MY_FLASHADDR = 0x40;
*(U16*)MY_FLASHADDR = 0xAA55;//写入AA55值
}

通过以上这两个C函数,我们既可以用来测试Flash,又可以通过整片擦除的方式清空整片Flash,从而可以将操作系统的镜像文件写入Flash。
n Boot from FLASH命令
这也就是利用XIP技术在Flash中启动操作系统,而不是将它加载到RAM后运行。
4 实现一个 BootLoader

1.BootLoader的构成组件

BootLoader主要由以下两部分组成。
(1)OEM startup code
这部分代码是在BootLoader中最先被执行的。它的主要功能是初始化最小范围的硬件设备,比如设置CPU工作频率、关闭看门狗、设置cache、设置RAM的刷新率、填写内存控制寄存器(通知CPU有效的数据总线引脚数)等。由于系统刚刚启动,不适合使用复杂的高级语言,因此这部分代码主要由汇编程序完成。在汇编程序段设置完堆栈后,就跳转到C语言的Main函数入口(位于<PLATNAME>/eboot/main.c);
(2)Main code
这部分代码由C语言实现,是BLCOMMON代码的一部分,它可以用来执行比较复杂的操作。比如检测内存和Flash的有效性、检测外部设备接口、检测串口并且向已经连接的主机发送调试信息、通过串口等待命令、启动网络接口、建立内存映射等汇编无法完成的工作。
Main code包含以下代码段:
Image download代码段
Image的下载可以通过以下接口。
① A. Parallel port I/O接口。这是通过主机和目标机的并口之间的数据传输来完成下载工作。具体实现代码,读者可以参考/kernel/hal/mdppfs.c文件;
② Ethernet port I/O接口。这是通过主机和目标机的网络接口之间的数据传输来完成下载工作。具体实现代码,读者可以参考WINCE420/public/common/oak/DRIVE|RS/ETHDBG/EBOOT文件夹;
③ Debug serial I/O接口。这是通过主机和目标机的串口之间的数据传输来完成下载工作。利用串口来传输的缺点非常明显,那就是速度太慢;
通过事先Flash write代码将镜像文件固化入Flash。只要BootLoader被设计成能从Flash加载镜像文件,本选项就可以使用。

2.BootLoader控制流函数Control Flow Functions

图9-5为Windows CE.NET的BootLoader的整体架构。其中列出了各种控制流函数。



图9-5 Boot Loader的整体架构
下面,我们来看以下Windows CE.NET的BootLoader在系统启动时所执行的函数。
图9-5中的所有函数分为3个模块:BLCOMMON、Download Function、FLASH Function。其中BLCOMMON模块是由微软公司提供的,执行一些逻辑上的功能,因此建议开发人员不要对其进行修改。而Download Function、FLASH Function中的函数与硬件平台息息相关,因此对于每种硬件平台都要将函数的实现进行修改。
BLCOMMON模块的功能解释。
BLCOMMON是一个库,其实现代码位于%_WINCEROOT%/Public/Common/Oak/Drivers/
Ethdbg/Blcommon目录下。它实现了Windows CE.NET BootLoader的基本框架。这个库的工作为:将bootloader加载到RAM中执行、解压缩.bin文件、校验硬件平台的完整性、对加载的进度进行跟踪。在BLCOMMON阶段执行的过程中,主要使用OEM函数集。
BLCOMMON库的入口点为BootloaderMain函数,它有Startup汇编函数完成后跳转至该入口。BLCOMMON库将被BootLoader的程序链接在一起。
在系统启动时,CPU首先执行StartUp函数,这是个由汇编实现的函数。StartUp函数主要的功能为:设置CPU工作频率、关闭看门狗、设置cache、设置RAM的刷新率、填写内存控制寄存器(通知CPU有效的数据总线引脚数)等。在StartUp完成任务后,就跳转到BootLoaderMain函数中。这个是由C语言编程实现的函数入口点。

下面是SMDK2410中的BootLoader中的main函数实现代码:

void main(void)
{
//清空LED
OEMWriteDebugLED(0, 0xF);
//通用BootLoader (blcommon)主入口
BootloaderMain();
//注意,在此调用了BootloaderMain函数,并且没有返回值
SpinForever();
}

(1)BLCOMMON模块函数
下面列举出BLCOMMON中的控制函数并分析它们,这些函数在Blcommon.h中声明,代码实现在Blcommon.lib里:
n OEMDebugInit函数:
在运行BootloaderMain程序后,将首先调用OEMDebugInit函数,它用来初始化调试信息的I/O设备,最常见的是串口设备。由于RS232协议简单性,在系统没有启动前对串口初始化较适用。在OEMDebugInit里,又通常调用OEMInitDebugSerial函数来初始化串口。
n OEMPlatformInit函数
OEM层的初始化函数,它主要负责目标机上的硬件初始化。在汇编阶段只是初始化了很小一部分硬件,这是由于BootLoader要求处理时间短,因此在汇编阶段的硬件初始化是十分简单的。所以有必要用高级语言完成对目标机的硬件设置,这包括具体的时钟设置、驱动和传输设备接口的初始化。
下面是此函数代码实例:

BOOL OEMPlatformInit(void)
{
BYTE BootDelay;
BYTE KeySelect;
EBOOT_CFG EbootCfg;
DWORD dwStartTime, dwPrevTime, dwCurrTime;
PCI_REG_INFO NANDInfo;
EdbgOutputDebugString("Microsoft Windows CE Bootloader for the Samsung SMDK2410 Version %d.%d Built %s/r/n/r/n", EBOOT_VERSION_MAJOR, EBOOT_VERS
ION_MINOR, __DATE__);
//初始化LCD显示器
InitDisplay();
// 初始化驱动全局区域
memset(pDriverGlobals, 0, sizeof(DRIVER_GLOBALS));
pDriverGlobals->MajorVer = DRVGLB_MAJOR_VER;
pDriverGlobals->MinorVer = DRVGLB_MINOR_VER;
pDriverGlobals->eth.EbootMagicNum = EBOOT_MAGIC_NUM;
// 初始化Flash,SMDK2410上的FLASH为AMD AM29LV800型。
if (!AM29LV800_Init(AMD_FLASH_START))
{
RETAILMSG(1, (TEXT("ERROR: OEMPlatformInit: Flash 初始化
failed./r/n")));
return(FALSE);
}
........
// 让用户选择启动选项
while((dwCurrTime - dwStartTime) < EbootCfg.BootDelay)
{
KeySelect = OEMReadDebugByte();
......
}
switch(KeySelect)//判别用户命令
{
case 0x20: // 根菜单项
g_bDownloadImage = MainMenu(&EbootCfg);
break;
case 0x00: //无按键失败
case 0x0d: //用户取消了倒计时
default:
if (EbootCfg.ConfigFlags & CONFIG_FLAGS_AUTOBOOT)
{
EdbgOutputDebugString ( "/r/nLaunching flash image ... /r/n");
g_bDownloadImage = FALSE;
}
else
{
EdbgOutputDebugString ( "/r/nStarting auto-download ... /r/n");
g_bDownloadImage = TRUE;
}
break;
}
//如果用户指定了静态IP地址,那么就使用静态IP地址(不使用DHCP)
if (g_bDownloadImage && !(EbootCfg.ConfigFlags & CONFIG_FLAGS_DHCP))
{
pDriverGlobals->eth.TargetAddr.dwIP = EbootCfg.IPAddr;
pDriverGlobals->eth.SubnetMask = EbootCfg.SubnetMask;
}
//配制以太网控制器
if (!InitEthDevice(&EbootCfg))
{
DEBUGMSG(1, (TEXT("OEMPlatformInit: Failed to initialize Ethernet
controller./r/n")));
return(FALSE);
}
return(TRUE);
}

n OEMPreDownload函数
在下载操作系统前执行这个函数,它可以用来设置如何进行Image文件下载。例如,可以设置成从网络下载或者跳过下载直接加载Flash中的Image文件。
下面是此函数代码实例:

DWORD OEMPreDownload(void)
{
CHAR szDeviceName[EDBG_MAX_DEV_NAMELEN];
BOOL bGotJump = FALSE;
DWORD dwDHCPLeaseTime = 0;
PDWORD pdwDHCPLeaseTime = &dwDHCPLeaseTime;
DWORD dwBootFlags = 0;
//如果用户想进入已存在的映像,那么跳过下载
if (!g_bDownloadImage)
{
g_bWaitForConnect = FALSE; // 不等待宿主机连接
return(BL_JUMP);
}
//如果用户想用一个静态IP地址,那么就不要从DHCP服务器请求一个地址
//将DHCP租期时间变量设置为NULL
if (pDriverGlobals->eth.TargetAddr.dwIP &&
pDriverGlobals->eth.SubnetMask)
{
pdwDHCPLeaseTime = NULL;
RETAILMSG(1, (TEXT("INFO: Using static IP address %s./r/n"),
inet_ntoa(pDriverGlobals->eth.TargetAddr.dwIP)));
RETAILMSG(1, (TEXT("INFO: Using subnet mask %s./r/n"),
inet_ntoa(pDriverGlobals->eth.SubnetMask)));
}
//创建基于以太网地址的设备名称(也就是Platform Builder如何定义设备)
//
memset(szDeviceName, 0, EDBG_MAX_DEV_NAMELEN);
CreateDeviceName(&pDriverGlobals->eth.TargetAddr, szDeviceName,
PLATFORM_STRING);
EdbgOutputDebugString("INFO: Using device name: '%s'/n", szDeviceName);
//初始化TFTP传送
//
if (!EbootInitEtherTransport(&pDriverGlobals->eth.TargetAddr,
&pDriverGlobals->eth.SubnetMask,
&bGotJump,
pdwDHCPLeaseTime,
EBOOT_VERSION_MAJOR,
EBOOT_VERSION_MINOR,
PLATFORM_STRING,
szDeviceName,
EDBG_CPU_ARM720,
dwBootFlags))
{
return(BL_ERROR);
}
//保存DHCP租期时间(注意,本例中使用的是静态IP)
pDriverGlobals->eth.DHCPLeaseTime = dwDHCPLeaseTime;
return(bGotJump ? BL_JUMP : BL_DOWNLOAD);
}

n DownloadImage
这个函数将执行把操作系统Image文件下载到目标机的操作。
n OEMLaunch函数
这个函数将PC指针直接设置到Image文件的开始地址,它是启动操作系统前BootLoader的最后一个函数,没有返回值。在此之后,BootLoader就消失了。
下面分析SMDK2410的OEMLaunch的实现代码:

void OEMLaunch(DWORD dwImageStart, DWORD dwImageLength, DWORD dwLaunchAddr,
const ROMHDR *pRomHdr)
{
DWORD dwPhysLaunchAddr;
EDBG_OS_CONFIG_DATA *pCfgData;
EDBG_ADDR EshellHostAddr;
EBOOT_CFG EbootCfg;
//从flash得到eboot配制
ReadEbootConfig(&EbootCfg);
//下载并从服务器得到IP和端口设置后,等待Platform Builder连接
//连接也发送KITL标志,稍后将用于OS(KITL)
if (g_bWaitForConnect)
{
……
}
//如果该下载未提供一个地址,那么记住kernel的启动地址或者重新调用保存的地址、、
//(也就是不下载kernel区域)
if (dwLaunchAddr && (EbootCfg.LaunchAddress != dwLaunchAddr))
{
EbootCfg.LaunchAddress = dwLaunchAddr;
WriteEbootConfig(&EbootCfg);
}
else
{
dwLaunchAddr = EbootCfg.LaunchAddress;
}
//如果用户请求一个储存在flash中的RAM映像,可以马上去请求.对于多个RAM BIN文件
//需要将RAM地址映射到flash地址
// RAM中基于地址偏移的映像在flash中是连续的
if (g_bDownloadImage && (EbootCfg.ConfigFlags & CONFIG_FLAGS_SAVETOFLASH))
{
if (!WriteRegionsToSmartMedia(&EbootCfg))
{
EdbgOutputDebugString("WARNING: OEMLaunch: Failed to store image
to Smart Media./r/n");
}
}
//跳到下载的映像(物理地址,因为马上将关闭MMU)
dwPhysLaunchAddr = ToPhysicalAddr(dwLaunchAddr);
EdbgOutputDebugString("INFO: OEMLaunch: Jumping to Physical Address 0x%Xh
(Virtual Address 0x%Xh).../r/n/r/n/r/n", dwPhysLaunchAddr, dwLaunchAddr);
Launch(dwPhysLaunchAddr);
//应该从不返回
SpinForever();
}

(2)下载模块函数
下载函数是由DownloadImage函数调用的。
下面列出下载模块函数并解释它们。
n OEMReadData
BLCOMMON调用这个函数从文件的传输器中读取数据。读者可以参看Public/Common/
Oak/Ethdbg/Eboot/Ebsimp.c文件中网络传输的例子
n OEMShowProgress
BLCOMMON在下载操作系统镜像文件的时候调用这个函数。在这个函数中,可以实现通知用户下载状态的各种手段比如可以用LED灯交替闪烁或者向主机的串口发送进度信息等。
n OEMMapMemAddr
如果目标系统的需求是要能支持把操作系统的镜像文件下载到FLASH中去,就必须调用本函数。由于FLASH操作速度比RAM慢,在片擦除的时候甚至会使读写操作停滞,这样在每次下载操作系统镜像文件时,由于FLASH的擦写都会使下载停滞。而OEMMapMemAddr使用了RAM缓冲操作系统镜像文件的方式,使得用户在下载操作系统镜像文件时感觉不到停滞,这个函数将FLASH地址映射到RAM地址,这样向FLASH写的数据实际上先被缓冲到RAM中,然后再写到FLASH中。
(3)FLASH编程模块
FLASH函数用于对不同的FLASH存储器进行编程。开发人员需要实现微软公司提供的框架里的函数。
n OEMIsFlashAddr函数
判别地址是否为有效的FLASH地址。注意,这里的FLASH地址与平台相关的,如S3C2410芯片和PXA255芯片的FLASH地址是不一样的,即便是同一款CPU,由于硬件结构的不同(FLASH大小、位置等)FLASH地址也不尽相同。
n OEMStartEraseFlash函数
BLCOMMON在获取FLASH的实际大小和开始地址后,将立即调用这个函数。这个函数将进行FLASH的擦除工作。
n OEMContinueEraseFlash函数
BLCOMMON在下载操作系统镜像文件的过程中可以调用这个函数。当FLASH擦除发生错误的时候,可以用这个函数来重复擦除操作,并且进行校验。
n OEMFinishEraseFlash函数
FLASH擦除完成时,BLCOMMON调用这个函数。这个函数将校验所有的擦除工作是否完成。
n OEMWriteFlash函数
调用这个函数,将缓冲在FLASH_CACHE中的操作系统镜像文件写入FLASH中。

3.代码分析

(1)Startup代码分析
在/PLATFORM/XSC1BD/EBOOT/ARM/fwp2.s文件中定义了BootLoader启动时执行的汇编指令:

GBLL ETHBOOT
ETHBOOT SETL {TRUE}
INCLUDE ..//..//kernel//hal//arm//fwxsc1.s
END
这里将..//..//kernel//hal//arm//fwxsc1.s中的代码引入。
kernel//hal//arm//fwxsc1.s:
; --- Setup interrupt / exception vectors
STARTUPTEXT
LEAF_ENTRY StartUp

IMPORT main
B Reset_Handler
B Undefined_Handler
B SWI_Handler
B Prefetch_Handler
B Abort_Handler
NOP ;
B IRQ_Handler
B FIQ_Handler ;
SWI_Handler
IF PLAT_LUBBOCK = "1"
ldr r1, =0x01000000
ldr r2, =FPGA_REGS_BASE_PHYSICAL
str r1, [r2, #HEXLED_OFFSET]
ENDIF
Prefetch_Handler
IF PLAT_LUBBOCK = "1"
ldr r1, =0x00100000
ldr r2, =FPGA_REGS_BASE_PHYSICAL
str r1, [r2, #HEXLED_OFFSET]
ENDIF
Abort_Handler
; 在此处不保存状态

IF B_STEP_PXA2X0 = "1"
; 在步骤B中,只对PXA250或PXA210芯片做这个操作
mrc p15, 0, r3, c15, c1, 0 ;Get Reg15 of CP15 for Access to CP7
; **********************************
IRQ_Handler
IF PLAT_LUBBOCK = "1"
ldr r1, =0xABCDEFAB
ldr r2, =0x08000010
str r1, [r2]

ENDIF
b IRQ_Handler
; **********************************
FIQ_Handler
IF PLAT_LUBBOCK = "1"
ldr r1, =0x00000100
ldr r2, =FPGA_REGS_BASE_PHYSICAL
str r1, [r2, #HEXLED_OFFSET]
ENDIF
FIQ_STAY
b FIQ_STAY
; **********************************
ALIGN 32
Reset_Handler
bl INITGPIO
bl INITMEMC
bl INITINTC
bl INITCLKS
bl INITOST
bl INITRTC
bl INITPWRMAN
bl ENABLECLKS
bl INITPLATFORM
bl DISPLAY_FREQS
b INITMMU

(2)Main函数代码分析
BLCOMMON的实现代码在/WINCE420/PUBLIC/COMMON/OAK/DRIVERS/ETHDBG/
BLCOMMON目录下的blcommon.c文件中。

void BootloaderMain (void)
{
ROMHDR *pRomHdr = NULL; // 镜像文件的PTOC
DWORD dwAction, dwpToc;
DWORD dwImageStart = 0, dwImageLength = 0, dwLaunchAddr = 0;
BOOL bDownloaded = FALSE;

if (!KernelRelocate (pTOC)) {
HALT (BLERR_KERNELRELOCATE);
}

//初始化debug支持,然后就可以用OEMWriteDebugString了
if (!OEMDebugInit ()) {
HALT (BLERR_DBGINIT);
}

//输出版本
EdbgOutputDebugString (NKSignon, CURRENT_VERSION_MAJOR, CURRENT_
VERSION_MINOR);

//初始化平台(时钟、驱动等)
if (!OEMPlatformInit ()) {
HALT (BLERR_PLATINIT);
}

//准备下载
EdbgOutputDebugString ("System ready!/r/nPreparing for download.../r/n");

//调用OEM指定的pre-download函数
switch (dwAction = OEMPreDownload ()) {
case BL_DOWNLOAD:
//下载映像
if (!DownloadImage (&dwImageStart, &dwImageLength, &dwLaunchAddr))
{
//报告DownloadImage的错误
SPIN_FOREVER;
}
bDownloaded = TRUE;
//检查pToc信号("CECE")
if (*(LPDWORD) OEMMapMemAddr (dwImageStart, dwImageStart +
ROM_SIGNATURE_OFFSET) == ROM_SIGNATURE) {
EdbgOutputDebugString("Found pTOC signature./n");
}
else
{
EdbgOutputDebugString
("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!/r/n");
EdbgOutputDebugString
("!! ERROR: Unable to find a table of contents in the downloaded image. If !!/r/n");
EdbgOutputDebugString
("!! this is a multi-region image, move the chain file region to an address !!/r/n");
EdbgOutputDebugString
("!! following the kernel region. Aborting.!/r/n");
EdbgOutputDebugString ("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!/r/n");
//如果没有信号,将永远循环下去
HALT (BLERR_SIGNATURE);
}
dwpToc = *(LPDWORD) OEMMapMemAddr (dwImageStart, dwImageStart +
ROM_SIGNATURE_OFFSET + sizeof(ULONG));
// 为了修正指针,需要再次映射一次
dwpToc = (DWORD) OEMMapMemAddr (dwImageStart, dwpToc + g_dwROMOffset);
//注意:必须复制或删除CLEAN_BOOT标志
memcpy (pRomHdr = &romhdr, (LPVOID) dwpToc, sizeof(ROMHDR));
EdbgOutputDebugString ("ROMHDR at Address %Xh/r/n", dwImageStart +
ROM_SIGNATURE_OFFSET + sizeof (DWORD)); // right after signature
EdbgOutputDebugString ("RomHdr.ulRAMStart=%Xh RomHdr.physfirst=%Xh./r/n",
romhdr.ulRAMStart, romhdr.physfirst);
//失败
case BL_JUMP:
//跳到映像之前,可以选择检查映像信号
//注意:如果现在没有下载的映像,那么在OEMLaunch中将假定从本地存储装载
//是RAM中残留的以前的下载,这种情况下,.映像启动地址必须是0.
//也就是映像信号程序将需要在存储或者RAM中寻找映像进行验证.
//因此OEM的OEMLaunch函数始终需要这样做.
//
if (g_pOEMCheckSignature)
{
if (!g_pOEMCheckSignature(dwImageStart, g_dwROMOffset, dwLaunchAddr, bDownloaded))
HALT(BLERR_WHQL_SIGNATURE);
}
//最后启动镜像,从不返回
OEMLaunch (dwImageStart, dwImageLength, dwLaunchAddr, pRomHdr);
// 从不返回
default:
HALT (BLERR_INVALIDCMD);
}
}

(3)FLASH操作代码分析
由于FLASH操作函数比较多,在此我们分析其中的几个有代表性的函数。
BOOL OEMIsFlashAddr (DWORD dwAddr)
{
//特殊处理Eboot,Eboot是为RAM创建,但存在于flash中
if (fileType & BOOTLOADER)
{
return(TRUE);
}
if ((dwAddr & 0xF0000000) == 0x90000000 || (dwAddr & 0xF0000000) == 0xB0000000)
{
return(TRUE);
}
return(FALSE);
}

static UINT16 FlashErase(DWORD dwStartBlock, DWORD dwNumBlocks)
{
BlockLockInfo BlockLockInfo;
if (dwStartBlock >= g_FlashInfo.dwNumBlocks || (dwStartBlock + dwNumBlocks - 1) >= g_FlashInfo.dwNumBlocks)
{
EdbgOutputDebugString("FlashErase: block number outside valid range./r/n");
return(-1);
}

//Unlock全部需要删除的块
EdbgOutputDebugString("FlashErase: Unlocking flash block(s) [0x%x, 0x%x] (please wait): ", dwStartBlock, (dwStartBlock + dwNumBlocks - 1));
BlockLockInfo.StartBlock = dwStartBlock;
BlockLockInfo.NumBlocks = dwNumBlocks;
if (!FMD_OEMIoControl(IOCTL_FMD_UNLOCK_BLOCKS, (PBYTE)&BlockLockInfo, sizeof(BlockLockInfo), NULL, 0, NULL))
{
EdbgOutputDebugString("/r/nWARNING: Unable to unlock all flash blocks!/r/n");
}
else
{
EdbgOutputDebugString("Done./r/n");
}
EdbgOutputDebugString("Erasing flash block(s) [0x%x, 0x%x] (please wait): ", dwStartBlock, (dwStartBlock + dwNumBlocks - 1));
while (dwNumBlocks--)
{
if (!FMD_EraseBlock(dwStartBlock))
{
EdbgOutputDebugString("/r/nFlashErase: unable to erase block (0x%x)./r/n", dwStartBlock);
return(-1);
}
++dwStartBlock;
EdbgOutputDebugString(".");
}
EdbgOutputDebugString("Done./r/n");
return(0);
}
5 Windows CE标准BootLoader的需求
1.BSP必须在加载Windows CE BootLoader以及操作系统的时候有一个默认的模式。
(1)在系统启动时,即使用户不输入指令,也能自动地下载操作系统镜像文件。即便使用了启动菜单,也是有时间限制了,超过时间限制,BootLoader应当能自动地按照默认方式启动操作系统;
(2)BootLoader应当被固化在FLASH中;
(3)BootLoader应当使用BLCOMMON的架构;
(4)BootLoader应当提供FLASH擦写和BootLoader自我更新的功能。这就要求BootLoader要分段执行,在FLASH中执行时,主要把自身剩余的代码复制到RAM中,然后进入RAM中运行时就能更新在FLASH中的BootLoader镜像文件。如果一直在FLASH中执行,同时又更新FLASH中的数据,那样会引起程序的逻辑错误;
(5)BootLoader应当提供向线性地址的FLASH下载操作系统镜像文件,并且跳转到其镜像文件数据首地址的功能;
(6)BootLoader应当提供向线性地址的RAM下载操作系统镜像文件,并且跳转到其镜像文件数据首地址的功能;
(7)在Platform Builder中的功能控制流里出现的任何分支,BootLoader都应当提供支持;
(8)BootLoader应当能使用硬件提供的任何接口来下载操作系统镜像文件,如并口、串口、以太口以及硬盘。在Platform Builder 4.2自带的样例BootLoader中,都以以太口为默认的传输方式。
2.目标机硬件结构的设计应当兼顾软件性能。硬件结构设计是否合理,这关系到后续软件开发以及调试的效率。
(1)目标机的flash应当是可以被替换的;
(2)目标机应当提供足够的RAM和flash来支持debugging;
(3)目标机是否有LED灯之类操作简单的、可用于调试的设备。
3.BootLoader应当提供两个启动选项:从主机下载操作系统镜像文件以及从FLASH中固化的镜像文件启动。在下载操作系统镜像文件执行时,又分为两个步骤:先将镜像文件下载到RAM中,然后再固化到FLASH里。
以上便是符合Windows CE标准要求的BootLoader功能及其硬件支持。这些要求保证了BootLoader在各个平台间中逻辑结构上的一致性。
那么BootLoader是如何被写入裸板的呢?实现方法有以下几种方式。
(1)使用ADS软件和JTAG仿真器。先将BootLoader的镜像文件通过JTAG下载到目标机的RAM中,然后在ADS中运行FLASH的烧写软件,这样可以把RAM中的数据写入FLASH;
(2)使用专门的FLASH编程器,将BootLoader写如FLASH(注意,这时FLASH还没有插入目标机,不受CPU控制)然后将烧写完毕的FLASH插入目标机中。
(3)在BootLoader已经驻留在FLASH的情况下,可以通过BootLoader实现自我更新的功能。
6 编译BootLoader程序
BootLoader程序可以通过PB的集成编译环境编译链接,控制文件为.bib文件,下面是一个简单的BootLoader的.bib文件:

MEMORY
CLI 9fc00000 00050000 RAMIMAGE
RAM 80080000 00070000 RAM
CONFIG
COMPRESSION=ON
SRE=ON
ROMSTART=9fc00000
ROMSIZE=00020000
ROMWIDTH=32
ROMOFFET=000000
MODULES
Nk.exe $(_FLATRELEASEDIR).exe CLI

MEMORY部分,定义了生成的映像文件的目标地址,以及程序运行可以使用的内存空间。
CONFIG部分,COMPRESSION是否对目标代码进行压缩;SRE是否生成格式为sre的目标代码;ROMSTART与ROMSIZE、ROMWIDTH、ROMOFFSET共同定义了开发平台上存放BootLoader物理介质的起始地址、大小、宽度和偏移量;
MODULES部分,定义了BootLoader所包含的文件,一般就只有一个文件cli.exe。
编译过程中,首先用命令build-c编译生成文件cli.exe,然后用romimage cli.bib命令产生最后的映像文件cli.sre。
BootLoader文件的下载有很多种方法:可以通过仿真器下载;可以通过其他调试程序下载;还可以直接烧写到Flash中。需要说明的是,这些方法可能会要求不同的映像格式。在PB环境下,可以生成的有.sre格式、纯二进制格式(用于直接烧写Flash)以及和Windows CE映像一样的.bin格式。
编译步骤如下。
1.点击菜单中“Build”->“Open Build Release Directory”打开命令行;
2.在Platform命令行下,键入如下命令:Set WINCEREL=1;
3.进入BootLoader的目录cd %_TGTPLATROOT%/Eboot;
4.Build-cfs。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: