| |
对于系统来说,一般都需要一个bootloader来下载和引导操作系统,常用的bootloader有eboot,uboot以及vivi等,对于windows ce来说最理想的bootloader当然是eboot(我也做了利用uboot下载和引导ce,以后我会介绍)。下面我就把自己开发eboot的过程和大家分享。eboot的流程可以如下图所示:
1)eboot和nk公用一段起始代码fw.s,所以我们会在eboot文件夹下的arm子文件夹找到fw.s,里面就一句话: INCLUDE ..\\..\\kernel\\hal\\arm\\fw.s,对于这段起始代码我就不详细分析,无非是建立好中断向量表,设置好系统的工作频率,设置MMU等,然后就跳转到eboot的main函数
2)eboot的main函数在eboot文件夹的main.c里面,代码如下:
void main (void)
{
BootloaderMain ();
SPIN_FOREVER;
}
是不是觉得很简单,好像什么也没有做,但是注意这个BootloaderMain函数,这个就是eboot真正的main函数,这个函数在
$(_WINCEROOT)\PUBLIC\COMMON\OAK\DRIVERS\ETHDBG\BLCOMMON \blcommon.c里面,这个函数是微软的ce对eboot的通用函数,它会调用在eboot文件夹里面由OEM商或者自己写的一些函数。那既然我们知道了真正的main函数在哪里,那么下面我们跟着BootloaderMain走吧。
3)在BootloaderMain 函数里面首先执行KernelRelocate,这是把一些全局变量存放到ram里面去,这个函数不是很重要。
4)下面就是执行OEMDebugInit,看到OEM三个字母了没有,这就说明这个函数是OEM商,或者我们自己需要实现的,在eboot下的main函数里面可以找到这个函数,这主要是提供给blcommon一些回调函数如下所示:
BOOL OEMDebugInit()
{
// Assign callback functions to be usec by blcommon.
g_pOEMReportError = OEMReportError;//错误报告函数
g_pOEMVerifyMemory = OEMVerifyMemory;// 下载映象时检测内存是否正常
g_pOEMMultiBINNotify = OEMMultiBINNotify; //通知需要下载的所有bin文件
OEMInitDebugSerial();//初始化串口调试输出
return TRUE;
}
这些被调用的函数也是OEM商或者我们自己编写的。前面三个函数都可以在main.c里面找到,代码比较罗唆,而且基本上和硬件没有太大关系,我们看看最后一个初始化串口调试输出的函数,这个文件在D:\WINCE420\PLATFORM. \smdk2410eboot+rtc\KERNEL\HAL\debug.c里面,我这里是设置串口0为调试输出口,三星自带的用的是串口1,并且把波特率设置为115200,大家如果需要用串口0作为调试输出口可以参考我的修改:
#define UART0BaudRate 115200
void OEMInitDebugSerial(void)
{
volatile UART1reg *s2410UART0 = (UART0reg *)UART0_BASE;
volatile IOPreg *s2410IOP = (IOPreg *)IOP_BASE;
s2410IOP->rGPHCON &= ~((3 << 8) | (3 << 10));
s2410IOP->rGPHCON |= ((2 << 4) | (2 << 6)); //
s2410IOP->rGPHUP |= (1 << 2) | (1 << 3);
s2410UART0->rUFCON = 0x0; // Disable the fifo
s2410UART0->rUMCON = 0x0; // Disable AFC.
s2410UART0->rULCON = 0x3; // Normal mode, N81.
s2410UART0->rUCON = 0x245;
s2410UART0->rUBRDIV = ( (int)(S2410PCLK/16.0/UART0BaudRate + 0.5) -1 );
}
调用完这个调试输出初始化函数以后,eboot的调试信息就会从串口0出来(当然nk的调试信息也会从这个串口出来了,因为这一部分是和nk复用的^_^)
5)BootloaderMain调用完OEMDebugInit后就调用下一个函数了-OEMPlatformInit,这个函数也在eboot的main.c里面可以找到,主要是初始化你的硬件平台,包括设置RTC时钟,初始化一下你的 NANDflash,然后就是读TOC (table of contents),一般TOC都会烧到nand的block1里面,如果读TOC失败,就会用默认的参数重写TOC,读TOC这段代码比较简单,在 fmd.cpp里面,大家可以自己研究。然后就是进入倒计时,如果在你设置的延迟时间内按键盘的话就会进入BootMonitor这个函数,这个函数主要是输出eboot的选择菜单,根据你的选择进行操作,如果在延迟时间结束你没有按键盘的话就会根据你设置的是Download new(下载新的映象)还是Launch existing(加载在nand中的映象)来进行下一步操作,我们先看看BootMonitor这个函数,这个函数虽然代码很多,但是其实非常简单,就是根据你的输入来设置改变一些全局变量,eboot在后面会根据这些变量来进行相应的操作。
如果选择了下载映象,在OEMPlatformInit函数里会调用InitEthDevice初始化网卡,然后返回 true,InitEthDevice函数在ether.c里面,具体需要根据你使用的网卡,把一些接口提供给eboot,下面是我的 InitEthDevice函数,我使用的是DM9000网卡:
BOOL InitEthDevice(PBOOT_CFG pBootCfg)
{
USHORT wMAC[3];
PBYTE pBaseIOAddress = NULL;
DWORD dwMultiplier = 0;
// Boot CS8900.
//
if (!pBaseIOAddress)
{
// Use the MAC address programmed into flash by the user.
//
memcpy(wMAC, pBootCfg->EdbgAddr.wMAC, 6);
pfnEDbgInit = DM9000DBG_Init;
pfnEDbgGetFrame = DM9000DBG_GetFrame;
pfnEDbgSendFrame. = DM9000DBG_SendFrame;
pBaseIOAddress = (PBYTE)CS8900DBG_IOBASE;
dwMultiplier = CS8900DBG_MEMBASE;
memcpy(pDriverGlobals->eth.TargetAddr.wMAC, pBootCfg->EdbgAddr.wMAC, 6);
pDriverGlobals->misc.EbootDevice = (UCHAR)DOWNLOAD_DEVICE_CS8900;
}
// Initialize the built-in Ethenet controller.
//
if (!pfnEDbgInit((PBYTE)pBaseIOAddress, dwMultiplier, wMAC))
{
EdbgOutputDebugString("ERROR: InitEthDevice: Failed to initialize Ethernet controller.\r\n");
return(FALSE);
}
// Make sure MAC address has been programmed.
//
if (!wMAC[0] && !wMAC[1] && !wMAC[2])
{
EdbgOutputDebugString("ERROR: InitEthDevice: Invalid MAC address read from NIC.\r\n");
return(FALSE); }
memcpy(&pDriverGlobals->eth.TargetAddr.wMAC, &wMAC, (3 * sizeof(USHORT)));
return(TRUE);
}
DM9000DBG_Init; DM9000DBG_GetFrame; DM9000DBG_SendFrame;这几个函数都需要在你的网卡驱动里面实现,这里只需要把这几个函数提供给eboot就行了。
6)BootloaderMain下面就调用OEMPreDownload进行一些下载前的准备工作,之后就会调用 DownloadImage下载内核,下载完了后就调用OEMLaunch启动RAM里面的内核,注意OEMLaunch里面会需要和PB建立连接,如果我们要绕过PB下载nk(我在以前的文章里面已经介绍过实现的方法了)我们就需要屏蔽这段代码(在#ifndef SIMULATOR #endif之间)。
7)如果我们选择了Launch existing image,在eboot的OEMPlatformInit里就会利用ReadRamImageFromBootMedia或者 ReadKernelRegionFromBootMedia函数把nk从nand中读到ram里面,然后再启动内核。第一个函数是直接把内核从nand 中拷贝到RAM里面,第二个函数必须要选择了支持binfs文件格式,它会把nand进行格式化成binfs,这些对nand操作的函数都再fmd.cpp里面,具体实现可以参考里面的代码。
8)以上介绍的都是eboot中比较重要的部分,其实在eboot的选择菜单里你还可以选择格式化nand,设置ip,mac地址等,总的来说,eboot的功能还是很强大的,但是和uboot比起来还是差了很多,因为它在人机交互上面做的不好,不如uboot那样强大。^_^!
Eboot代码流程
----by nasiry
转载请说明出处
eboot弄了很多次了,一直都没有整理一下整个代码流程。这次还是事来做一下吧:
首先通常都是汇编代码:启动时由系统复位导致PC为0为触发条件:以2440代码为例直接进入fw.s文件。主要执行的操作为设置处理器频率(PLL),设置内存参数,须注意的是在该部分代码虽然在形式上实现了诸多中断向量,但是这些代码根本上不会得到执行。(参考“Eboot 编译编译器决定中断向量及其实现单一性的原因”)而在后面会有一些其他手段用于实现中断向量的功能。由于和hal复用该部分文件,所以调用的函数名称会与内核启动函数的相同(如:KernelStart),但是实现内容却是完全不同的。在该部分的最后还将保存一个OEMAddressTable的地址指针到下一格函数,进行应有的内存影射。下面还是以2440bsp为例说明。根据windowsCE系统的要求,需要把将会操作到的内存空间影射到0x8000 0000后的512M空间中,其中低256M为带缓冲的,高256M则是不带缓冲的。不带缓冲的区域通常是驱动访问设备必须的,这样可以保障对硬件的操作不受到缓冲的干扰。除了按照OEMAddressTable进行内存空间影射外,还可以要根据程序需求进行其他一系列影射,通常的做法都是将目前所有的设备/内存在原地址空间上再影射一次,以方便使用。另外就是在将0x0位置影射到内存,这样,就可以通过这个部分来进行中短向量的安装了,以实现中断服务程序。(以上内存影射并不是必须的,仅仅是为了方便程序的编写而已,使用和系统相同的影射表可以使得不必重新另外建一套头文件,同时如果需要的话可以一直在内存中保存eboot,这样内存空间的规划也会相对容易做一些)。
以上的这些工作做完,就进入windowsCE提供的eboot入口函数了。也就是BootloaderMain。这个函数位于WINCE420\PUBLIC\COMMON\OAK\DRIVERS\ETHDBG\BLCOMMON\blcommon.c文件中。该目录的文件就是微软提供的eboot框架,尽管这不是实现的部分,也一并分析了吧。这部分的内容一开始就很有意思,第一个执行的语句如下
if (!KernelRelocate (pTOC)) { HALT (BLERR_KERNELRELOCATE); }
而这个传入的参数却这样定义:ROMHDR * volatile const pTOC = (ROMHDR *)-1; 按照程序的内容来说,这个函数到这里一定是死循环了,可是事实上这个死循环不会得到执行,也就是pTOC = (ROMHDR *)-1并没有真正的起到作用。 在pTOC第一次使用前检查了一下pTOC的值,结果如下:pTOC=0x8C04D694。也就是说pTOC在没有被改变之前就已经不再是-1了,而且 pTOC是被定义成const的,也没有办法去改变,看样子就只能是编译器的过程中这个指针就已经被动了手脚。而查看eboot.map中该地址所对应的内容居然是bootpart.obj的内容。感觉是无从下手了,这样子让人太费解了。仔细比较eboot.exe和eboot.nb0后发现,eboot.exe通过romimage处理后长度增加了,而且结构上改变了许多,在原.eboot.exe后增加了一系列原来没有内容,pTOC的内容就是属于这部分内容中的一部分。由于牵涉到windowsCE的Image Chain结构,所以无法继续往下分析,这部分的内容就先跳过。以后再回头来看。
随后在 BootloaderMain中对调试界面进行初始化,由OEMDebugInit完成这个函数的实现是由具体的硬件决定的通常来说为串口的初始化,再次以后就通过该调试界面进行调试信息的发布。
然后在OEMPlatformInit()中对eboot所要用到的硬件资源进行初始化设备,通常这些会包含:存储设备(FLASH、硬盘等)、传输界面(以太网卡、USB-RNDIS、无线网卡、CF—LAN等等)、系统时钟,为下面将会进行的os镜像传输、os镜像存储、读取等等一系列动作做好准备。
做好准备以后就可以调用OEMPreDownload ()来进行传输OS的准备了,在这个函数中通常会实现一系列功能,诸如:设备的设置、存储设备的格式化、网卡IP的指定、等等。windowsCE所支持的标准操作则只是两个,一个是跳转、一个是下载、分别对应下载OS镜像和跳入OS的入口点,前面提到的那些功能都是自己的扩展实现。
下面我们分别来看这两个标准操作的分支:首先是下载的分支,在下载的分支中首先通过OEMReadData来获取在最先收到的标示位也就是被称为魔法数的数据,用以比较确认该传输的内容是否为windowsCE的镜像数据,随后继续进一步获取将接收的数据的效验和,待接收的镜像的数量,镜像起始地址、长度等信息,有了这些个信息,很自然地就是接收镜像了,
随后检查数据的效验和。下一个镜像的信息的接收,如此往复循环直至所有的镜像信息接收完毕。