启动程序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通信方式,串口中的启动功能等。
最常见的嵌入式系统目标机和主机通信的模型。开发人员可以使用超级终端通过串口向目标机发送命令,由于串口协议是最简单、可靠的,因此经常被使用在系统未启动前的阶段。
串口命令行选项

超级终端上使用串口协议,用文本形式来显示和目标机的通信过程。
用户可以在超级终端的命令行中指定目标机执行某个命令。这些命令包括。
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函数入口(位于<LATNAME>\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
Windows CE.NET的BootLoader的整体架构。
http://book.csdn.net/BookFiles/131/09/image008.jpg
面,我们来看以下Windows CE.NET的BootLoader在系统启动时所执行的函数。
图中的所有函数分为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。




阅读(2)
分享(0)
评论(0)
分类:WinCE
 发表于21:34



正在加载评论...


[07/30] Platform Builder 4.2 定制资料
   [折叠]

1 建立配置                                                               
        Windows CE的软硬件环境配置和调试Windows CE需要1台高性能开发工作站和1台目标调试设备。Platform Builder 4.2运行在Windows 2000或者以上版本的高性能开发工作站上。Platform Builder提供了创建和调试Windows CE映射NK.BIN的集成开发环境,如向导和工具栏,支持活动模板库(ATL)、微软基本类库(MFC)等,为支持的处理器提供编译器、内核调试器以及各种远程调试工具。目标调试平台可以是经过适当配置以后的普通PC机,也可以是嵌入式微处理器模块。开发工作站通过以太网或者串、并行口与目标平台连接,并将Windows CE操作系统映像文件NK.BIN下载到目标平台。下载完成之后,Windows CE在目标平台启动。在开发工作站上,可以使用Platform Builder提供的调试工具查看Windows CE的运行情况。以太网和串、并行口这两种连接类型的差别是:以太网连接,下载速度较快;串、并行口连接,下载速度较慢。开发工作站需要以太网网卡,目标平台需要NE2000兼容的调试以太网网卡 串行口、并行口映像文件和调试工具都使用以太网 映像文件使用并行口下载,目标平台的调试信息使用串行口传送。
2 Platform Builder 3.0提供的配置文件
       配置文件包括4种文件类型:二进制映像生成文件.BIB、注册表文件.REG、目录和文件配表文件.DAT、数据库文件.DB。通过修改这些配置文件可以裁剪优化Windows CE。用户根据需要可以创建自己的配置文件,PB提供的主要配置文件如下表所列。
       配置文件列表
.BIB文件 .REG文件 .DAT文件 .DB文件
Common.bib Common.reg Common.dat Common.db
Dcom.bib Dcom.reg Servers.dat  
Msmq.bib Msmq.reg    
Platform.bib Platform.reg Platform.dat Platform.db
Project.bib Project.reg Project.dat  
Wceshellfe.bib Wceshellfe.reg Wceshellfe.dat Wceshellfe.db
Wceappsfe.bib Wceappsfe.reg Wceappsfe.dat Wceappsfe.db
Config.bib Script reg    
2.1 二进制映像生成文件.BIB
        .BIB文件是文本文件,标识包含在Windows CE映像中的目标模块、组件和文件。一个.BIB文件包含4个部分:FILES、MODULES、MEMORY和CONFIG。
(1)FILES部分
FILES分配一部分内存空间给静态数据文件。这部分主要包括字体文件(.TTF)、文本文件(.TXT)、位图文件(.BMP)和声音文件(.WAV)等。例如:
;Name Path Memory Type
;------ --------------------------------------- ----------- -----
Tahoma.ttf $(_FLATRELEASEDIR).ttf NK SHU
Name:在Windows CE系统中的文件名。
Path:文件在开发工作站中的位置。
Memory:文件所属的内存区。
Type:定义文件属性。Windows CE中的文件有以下文件属性:S为系统文件、H为隐含文件、U为非压缩文件、R为压缩资源文件、C为压缩文件。1个文件可以同时具有几种属性。
       上例说明NK内存区包含开发工作站的$(_FLATRELEASEDIR).ttf文件。在Windows CE系统中,tahoma.ttf的文件属性是系统隐含的非压缩文件。
(2)MODULES部分
列出加载在内存中的目标模块和组件,包括所有的可执行文件.EXE和动态链接库.DLL。例如:
;Name Path Memory Type
;------- --------------------------------------- --------- ------
device.exe $(_FLATRELEASEDIR).exe NK SH
device模块加载到内存区。Windows CE系统中device.exe具有系统和隐含属性。
(3)MEMEORY部分
把物理内存分成2个部分:数据存储区和程序存储区。例如
;Name Start address size(bytes) Type
;------- -------------- ------------ --------------
NK 80200000 00600000 RAMIMAGE
RAM 80800000 00800000 RAM
在Windows CE系统中,内存分成3种类型:RAM、RAMIMG和RESERVED。RAM规定内存大小;RAMIMG规定ROM空间;RESERVED为保留的内存空间,内核不能使用这部分内存,但是应用程序可以访问。上例说明NK的起始地址是80200000,它占有6MB内存空间,作为ROM用于数据存储区。RAM在80800000开始,占有8MB的内存空间,用于程序存储区。
(4)CONFIG部分
这部分不是必不可必的。在这部分进行一些属性设置,如是否压缩和ROM大小等。
2.2 注册表文件.REG
       注册表文件建立操作系统映像的注册表入口。注册表是一个数据,存储了大量的配置信息,包括应用程序信息、设备驱动程序的配置、系统配置信息、用户参数和其它相关数据。当目标平台冷启动时,Makeimg.exe使用.REG文件创建默认注册表。Platform.REG配置与平台有关的注册表信息,如设备驱动程序入口。Project.REG设置与工程有关的注册表格。注册表文件的格式:
[HKEY_LOCAL_MACHINE]
“Launch10” = "shell.exe"
"Launch20" = "device.exe"
"Launch30" = "gwes.exe"
"Depend30" = "hex:14,00"
       上述注册表入口设置规定内核在启动时必须自动运行shell.EXE和device.exe模块,gwes.EXE模块必须在device.EXE正常启动以后才能运行。
2.3 数据库文件.DB
        Windows CE提供内置的轻量级数据库管理系统。Windows CE的属性数据库由1张包含记录的表组成,每个记录包含许多不同的属性,每个属性有3种信息:标志属性的ID号、类型和值。例如:
.Record:
Field:402001f:“{000214A0-0000-0000-C000-000000000046}”
Field:42020003:15
Field:42030003:2
End
        属性数据库是一种平面结构,不能定义属性数据库之间的关系。使用Microsoft ActiveX Data Object for Windows CE(ADOCE)技术可以容量地访问属性数据库,甚至可以使用SQL语句查询信息。
2.4 目录分配表文件.DAT
        .DAT文件定义Windows CE映像NK.NIB黑认的目录和文件结构。注意,Winodws CE不支持像Windows 9x/2000一样的盘符驱动器,而且没有当前目录的概念。例如:
root:-Directory("rogram Files")
Directory("rogram Files")irectory("MY Work")
root:-Directory("My Documents")
Directory("My Documents"):-File("MyFile.doc")
        生成2个根目录:Program Files和MyDocuments。Program Files有1个子目录:My Work。文件MyFile.DOC位于My Document目录中。注意MyFile.DOC必须出现在一个.BIB文件中,这样,映像文件中会有MyFile.DOC。
3 设置环境变量、生成映像文件NK.BIN
3.1 设置环境变量
       在所有配置文件中,都存在条件语句IF Enviroment Value……END IF。当条件成立时,IF和END IF之间的信息包含在映像文件NK.BIN中;条件不成立时,则不包含相关模块和文件。通过设置环境变量可以添加或者删除NK.BIN中的模块和组件。以.BIB文件为例:
;Name Path Memory Type
;-------- ------------------------------------ ---------- ------
IFIMGUSB
IF CEPC_UHCI
uhci.dll $(_FLATRELEASEDIR).dll NK SH
ENDIF
IF CEPC_OHCI
ohci.dll $(_FLATRELEASEDIR).dll NK SH
ENDIF
usbd.dll $(_FLATRELEASEDIR).dll NK SH
usbhid.dll $(_FLATRELEASEDIR).dll NK SH
ENDIF
        这里IMGUSB、CEPC_UHCI、CEPC_OHCI都是逻辑型环境变量。在PB中,可以使用2种方法设置环境变量。方法1:点击Platform菜单的Setting…,打开平台设置对话框,分别输入Variable和Value设置环境变量。方法2:点击Build菜单的Open Build Release Directory,打开命令提示窗口,在命令提示窗口中设置环境变量。在命令行中输入SETIMGUSB=1,动态链接库usbd.dll和usbhid.dll被包含到映像文件NK.BIN。uhci.dll和ohci.dll是否包含在NK.BIN中,取决于环境变量CEPC_UHCI和CEPC_OHCI的设置。如果SET IMGUSB=0,KN.BIN中不包含上述4个动态链接库。
3.2 Makeimg.exe工具
       生成映像文件NK.BIN是平台创建过程的最后一步,也是配置Windows CE的最终目标。Makeimg.exe使用全部配置文件把目标模块和文件合并成一个惟一的Windows CE映像文件NK.BIN。图1所示为NK.BIN的生成过程。
Makeimg生成NK.BIN的具体步骤:
①全并配置文件:所有的.BIB文件合并成CE.BIB。CE.BIB包含映像NK.BIN中的所有文件;所有.REG文件合并成REGINIT.INT;所有.DAT文件合并在修改配置文件时,一定要注意CE.BIB、REGINIT.INI、INITOBJ.DAT、INITDB.INI是中间文件,修改这些文件不能配置Windows CE。
②将REGINIT.INI文件压缩成一个二进制文件DEFAULT.FDF。
③根据环境变量COUNTRY的设置,替换模块中的资源,使Windows CE映像满足特定语言的需要,最后生成二进制映像文件NK.BIN。
4 修改配置文件添加RTL8139网卡驱动
        由于Windows CE具有完全不同于其它Windows操作系统的内核,在其它操作系统上运行的设备驱动程序不能在Windows CE上直接运行。原设备驱动程序代码必须经过适当修改或者全部重写以后才能在Windows CE系统上运行。Windows CE支持2种类型的设备驱动程序;本机设备驱动程序和流接口设备驱动程序。本机设备驱动程序通常与基于Windows CE的目标平台有着紧密的连接,而且每种本机设备驱动程序有精确的和特殊的目标的,这部分驱动程序一般由原始设备制造商(OEM)提供。流接口设备驱动程序是在用户一级的动态链接库(DLL),用于驱动可安装的外围部件。RTL8139网卡驱动程序是流接口设备驱动程序。使用Platform Builder或者Embedded Visual C++ 3.0生成RTL8139网卡驱动程序RTL8139.DLL.将(_FLATRELEASEDIR)中,分别修改Platform.BIB和Platform.REG。
①向Platform.BIB文件中添加下列语句:
MEMORY
RTL8139 8090000 00010000 RESERVED
MODULES
Rtl8139.dll $(_FLARTELEASEDIR).dll NK SH
②向Platform.REG中添加下列语句:
[HKEY_LOCAL_MACHINE]
“DisplayName”=“Realtek RTL8139 based Fast
Ethernet Driver”
“Group”=“NDIS”
“ImagePath” = “rtl8139.dll”
[HKEY_LOCAL_MACHINE]
“Route” = “Rmulti_sz: TL81391”
[HKEY_LOCAL_MACHINE]
“DisplayName” = “Realtek RTL8139 based Fast Ethernet Driver”
“Group” = “NDIS”
“ImagePath” = “rtl8139.dll”
[HKEY_LOCAL_MACHINE]
“BusNumber”= dword:0
“BusType”=dword:5
“ScanMethod”=dword:0
“SharedInterrupt”= dword:1
“PhysicalAddressBase”=dword:001a0000
[HKEY_LOCAL_MACHINE]
“EnableDHCP”=dword:0
;MULTI_SZ类型
“DefaultGateway”=“”
;0代表广播地址(或者255.255.255.255)
“UseZerBroadcast”=dword:0
;NULTI_SZ类型,IP地址
“IpAddress”=“202.2.4.77.16”
;MULTI_SZ类型,上面IP地址的子网掩码
“Subnetmask”=“255.255255.0”
[HKEY_LOCAL_MACHINE]
“Bind”=multi_sz:“RTL81391”
③Makeimg生成NK.BIN
       在命令提示窗口输入Makeimg命令,生成新的Windows CE映像NK.BIN。下载NK.BIN到目标平台,通过以太网电缆直接连接开发工作站网卡和目标平台的RTL8139网卡。FRTL8139网卡的IP地址必须和开发工作站的IP地址在同一子网中,这样,在开始工作站运行ping命令时才会有响应。

转载于:https://www.cnblogs.com/andyhere/archive/2008/10/24/1318927.html

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值