九鼎创展论坛

标题: S3C2440 eboot详细流程分析[本站原创] [打印本页]

作者: armeasy    时间: 2012-7-19 11:31
标题: S3C2440 eboot详细流程分析[本站原创]
模块
三星官方给出的Bootloader就包括这两个部分:NBoot和EBoot。针对S3C2440处理器,由于内部只有4K的SRAM,当硬件配置成Nand启动时,固化在处理器内部的IROM程序会自动将Nand的Block0的前4K字节的程序拷备到SRAM中并运行。可见,这时NBoot不能超过4KB。而实际的Bootloader要完成的工作,远不止4KB代码所能胜任的。因此,EBoot诞生了。正是由于从Nand启动时需要从Nand拷备一段bootloader,故将这段Bootloader称之为Nboot。而由于Nboot比较小,又产生了EBoot。Eboot字面上的意思是以太网Bootloader,而现在其功能早已经超出了它名字的范围,而且在WINCE领域,大部分都是采用的USB下载,而非以太网模式。
由于三星原厂以NBoot和EBoot两种Bootloader推广,因此,国内很多开发板商也都采用这两种方式,像扬创等。也有一些linux fans,将多用于linux的Uboot,VIVI等移植到了WINCE平台上来,即通过Uboot,VIVI引导NK。目前做得比较有名气的像飞凌,友善等。但是出于行业竞争优势,很多开发板OEM厂商都不提供完整的Bootloader,这一点另人很头疼。目前在网上能找到的完整的2440 Bootloader源代码,也只有优龙,飞凌等几种。正因为如此,针对K390平台,这里也采用了UBoot+EBoot的模式。
在WINCE4.2平台下,飞凌直接通过一个UBoot就搞定一切,不必再用什么Nboot+Eboot了,到了WINCE5.0平台下,飞凌额外添加了Eboot,看来Eboot的功能还是不可小觑。本身飞凌的Uboot编译下来,大概50KB左右的样子,那么2440从Nand启动,内部SRAM又只有4KB,它是怎么实现的呢?答案就在UBoot的引导程序2440init.s里面。开机时,IROM并不会将整个Uboot的代码全COPY到SRAM中,也无法全COPY过去,只是COPY了需要的一部分而已。这部分程序初始化中断向量表,看门狗,MMU,DRAM等之后,再将剩下的代码放到DRAM中运行。这样,整个UBoot就不再受限于4K大小了,也就是说,整个Bootloader完全可以在UBoot中完成了。

Uboot2.1.1Uboot功能
1):初始化CPU内部相关控制器,如设置GPIO,关闭Watch Dog,关闭中断,设置系统时钟;
2):初始化内存;
3):初始化串口,主要用来输出调试信息;
4):初始化NAND Flash;
5):配置启动参数;
6):初始化USB端口,用于下载文件映像;
7):初始化液晶屏;
8):下载Uboot本身,Eboot到Nand Flash;
9):格式化Nand Flash;
10):引导Eboot并运行;
11):通过键盘扫描决定启动模式或下载模式;
12):LCD实时显示更新提示信息。
2.1.2Uboot框架
   Uboot的整个工作流程如下:

上图为Uboot的总体程序框架,首先,在汇编语句中,会初始化中断向量表,PLL,Watchdog,中断,DRAM等,之后,会将Uboot的代码拷备到DRAM中运行。接着,在C语言的Main()函数中,初始化GPIO,中断相关寄存器,然后设置GPB8为output且为高电平,用于选择K390的USB Device作为Device口,用于程序下载。之后初始化调试串口1,K390将会串口1连到了RJ45接口,通过RS232转RJ45延长线,保证硬件板与PC机的串口连接。随后会从nand的配置信息区域读取配置信息,如果nand里面全是空的,要先将配置信息写进对应区域。配置信息记录了倒计时等相关信息。紧接着初始化LCD,开背光,这时LCD上会有相应显示了。
前面的准备工作做完了,这时程序开始判断外部条件,选择进入下载模式或是引导Eboot最终进入系统。当没有同时按下按键2,5,0时,Uboot马上引导Eboot,并执行Eboot程序。当同时按下按键2,5,0时,Uboot将会等待参数配置表中设置的时间延时,在指定时间内如果按下空格键(PC端),将进入人机交互界面,实现下载,擦除等功能。在指定时间内没有按下空格键,将引导Eboot,并执行Eboot程序。
进入人机交互界面后,打印如下信息:
1):Download toflash
2):Erase FlashPartition
3):Boot WINCE
4):Configparameters
这时按下“1”(指PC键盘的“1”,以后人机操作按钮如未作说明,全指PC机的键盘),进入flash下载界面,打印如下信息:
0:offset 0x00000000, size 0x00010000[boot]
1: offset 0x00014000,size 0x00020000 [eboot]
2: offset0x001a0000, size 0x00020000 [wince]
用户可以通过按0、1、2进入相应的下载模式,然后点击DNW的USBPost->Transmit,找到相应的uboot.bin,eboot.nb0,xip.bin下载对应的映像文件。值得注意的是,这里在更新wince时,按下3,程序会提示用户通过boot wince进入eboot,在eboot下更新wince映像,即xip.bin。
在主界面下,用户按下“2”,进入擦除flash相关区域的界面,打印如下信息:
0: offset 0x00000000, size 0x00010000 [boot]
1: offset 0x00014000, size 0x00020000 [eboot]
2: offset 0x001a0000, size 0x00020000 [wince]
用户通过按0、1、2,打印信息会提示是否擦除相应的块,按y擦除,按n取消擦除。
在主界面下,用户按下“3”,进入手动引导WINCE界面,这时程序会加载eboot到DRAM并运行eboot。具体NK的升级将通过eboot完成。

2.1.3 Uboot程序分析
1):配置信息结构体
//小于等于512个字节,最多保存24个ITEM和128字节用户定义的字符串
BootParams boot_params =
{
    {"auto-run", 3},   //0=bootwithout parameters,1=boot with parameters
    //{"cpuclk",  2}, //0=200M, 1=300M, 2=400M,3=440M
    {"rundelay", 0},   //0seconds
    {"serial",   0},   //0=serial port 0, 1=serial port 1
    //{"AppRunAddr",  0x32000000},
    {"baudrate", 115200},
    {"machine", 193},
    {"runAddr", 0x30201000},
    {"rootfs",   3},
    {"tty",      0},
{"displayS",  3/*0*/},//lqm changed.0=320*240  1=640*480 2= 800*600 3 = 800*480
    {"displayM",  0},  //0= lcd 1=vga    2=tv
    {"initrdA", 0x30200000},
    {"initrdL", 0x02000000},
    {"memsize", 0x04000000},
    //{"devfs",   1},
    //{"ostore",  0}, //0=nand, 1=nor
    {"userpara", sizeof(DEFAULT_USER_PARAMS)},
    DEFAULT_USER_PARAMS,
    {0}
};

typedef struct {
    ParamItem start;
    //ParamItem cpu_clk;
    ParamItem boot_delay;
    ParamItem serial_sel;
    //ParamItem AppRun_addr;
    ParamItem serial_baud;
    ParamItem machine;
    ParamItem run_addr;
    ParamItem root_sel;
    ParamItem tty_sel;
    ParamItem display_sel;
    ParamItem display_mode;
    ParamItem initrd_addr;
    ParamItem initrd_len;
    ParamItem mem_cfg;
    //ParamItem devfs_sel;
    //ParamItem osstor;
    ParamItem user_params;
    char string[128];
    unsigned int bpage[50];
} BootParams;
上面的结构体描述了具体的参数配置,这些参数最终会写入NandFlash,具体参数可根据需要重新分配。如上面的第二项:”rundelay”定义了Uboot启动时的延时时间。
2):液晶屏初始化函数
/**************************************************************
800×480 16Bpp TFT LCD功能模块初始化
**************************************************************/
static voidLcd_Init_800_480(void)
{
   unsigned short c;
   rGPCUP=0xffffffff; // Disable Pull-upregister
   //rGPCCON=0xaaaa56a9; //InitializeVD[7:0],LCDVF[2:0],VM,VFRAME,VLINE,VCLK,LEND
   rGPCCON=0xaaaa56aa;// lqm test for dispdriver debug.10-04-13
   rGPDUP=0xffffffff; // Disable Pull-upregister
   rGPDCON=0xaaaaaaaa; //Initialize VD[15:8]

   c = 0x0800;
   a070vw04_cmd(&c);// lqm added for AUO7'TFT DRVA PIN.
   c = 0x1800;
   a070vw04_cmd(&c);// lqm added for AUO7'TFT DRVA PIN.
   c = 0x2800;
   a070vw04_cmd(&c);// lqm added for AUO7'TFT DRVA PIN.
   c = 0x3800;
   a070vw04_cmd(&c);// lqm added for AUO7'TFT DRVA PIN.
   c = 0x4800;
   a070vw04_cmd(&c);// lqm added for AUO7'TFT DRVA PIN.
   c = 0x6800;
   a070vw04_cmd(&c);// lqm added for AUO7'TFT DRVA PIN.

   rLCDCON1=(CLKVAL_TFT_800_480<<8)|(MVAL_USED<<7)|(3<<5)|(12<<1)|0;
         //TFT LCD panel,12bpp TFT,ENVID=off
   rLCDCON2=(VBPD_800_480<<24)|(LINEVAL_TFT_800_480<<14)|(VFPD_800_480<<6)|(VSPW_800_480);
   rLCDCON3=(HBPD_800_480<<19)|(HOZVAL_TFT_800_480<<8)|(HFPD_800_480);
   rLCDCON4=(MVAL<<8)|(HSPW_800_480);
   rLCDCON5=(1<<11)|(1<<9)|(1<<8)|(1<<3)|(BSWP<<1)|(HWSWP);
   //rLCDCON5=(1<<11)|(0<<9)|(0<<8)|(0<<6)|(BSWP<<1)|(HWSWP);        //FRM5:6:5,HSYNC and VSYNC are inverted

   rLCDSADDR1=(((U32)LCD_BUFFER>>22)<<21)|M5D((U32)LCD_BUFFER>>1);
   rLCDSADDR2=M5D(((U32)LCD_BUFFER+(SCR_XSIZE_800_480*LCD_YSIZE_800_480*2))>>1 );
   rLCDSADDR3=(((SCR_XSIZE_800_480-LCD_XSIZE_800_480)/1)<<11)|(LCD_XSIZE_800_480/1);
   rLCDINTMSK|=(3); // MASK LCD Sub Interrupt
    //rTCONSEL|=((1<<4)|1); // DisableLCC3600, LPC3600
   rTPAL=0; // Disable Temp Palette
}
3):基于AUO7寸屏的参数配置如下:
/***************************************************
  800*480
****************************************************/
#defineLCD_XSIZE_800_480       (800)
#defineLCD_YSIZE_800_480       (480)
#define SCR_XSIZE_800_480      (800)
#defineSCR_YSIZE_800_480       (480)
#defineHOZVAL_TFT_800_480     (LCD_XSIZE_800_480-1)//分辨率
#defineLINEVAL_TFT_800_480    (LCD_YSIZE_800_480-1)
#defineVBPD_800_480                21               //垂直同步信号的后肩
#defineVFPD_800_480                 1                 //垂直同步信号的前肩
#defineVSPW_800_480                3                 //垂直同步信号的脉宽
#defineHBPD_800_480                88               //8水平同步信号的后肩
#defineHFPD_800_480                 40               //8水平同步信号的前肩
#defineHSPW_800_480                128              //6水平同步信号的脉宽
#defineCLKVAL_TFT_800_480     (1) //3
#define LCD_XSIZE  800
#define LCD_YSIZE  480
#define SCR_XSIZE  LCD_XSIZE
#define SCR_YSIZE  LCD_YSIZE
4):基于AUO 7寸屏的屏寄存器SPI通信读写函数如下:
static voidmosi(unsigned int n)
{
   //s3c2410_gpio_setpin(S3C2410_GPC6, n);
   rGPCDAT = (rGPCDAT & ~(1 << 6)) |(n << 6);
}

static voidspiclk(unsigned int n)
{
   //s3c2410_gpio_setpin(S3C2410_GPC7, n);
   rGPCDAT = (rGPCDAT & ~(1 << 7)) |(n << 7);
}

static voidnss(unsigned int n)
{
   //s3c2410_gpio_setpin(S3C2410_GPC5, n);
   rGPCDAT = (rGPCDAT & ~(1 << 5)) |(n << 5);
}

static voidspi_send_init(void)
{
   mosi(1);
   spiclk(1);//SCL=1
   nss(1);//CS=1
   //s3c2410_gpio_pullup(S3C2410_GPC5,0);//pullup enable
   //s3c2410_gpio_pullup(S3C2410_GPC6, 0);
   //s3c2410_gpio_pullup(S3C2410_GPC7, 0);
   rGPCUP = (rGPCUP & ~(3 << 5));
   //s3c2410_gpio_cfgpin(S3C2410_GPC5,S3C2410_GPIO_OUTPUT);//out
   //s3c2410_gpio_cfgpin(S3C2410_GPC6, S3C2410_GPIO_OUTPUT);
   //s3c2410_gpio_cfgpin(S3C2410_GPC7,S3C2410_GPIO_OUTPUT);
   rGPCCON = (rGPCCON & ~(0x3f << 10))| (0x15 << 10);
}

// 读寄存器
static voida070vw04_recv(unsigned short *cmd)
{
   int i;
   unsigned short data = *cmd;
   unsigned short recv = 0;

   unsigned short address = (*cmd &0xf000)>>12; // lqm added.

   for(i = 0; i < 5; i++)//先送4位地址数据,再送读标识位1.
   {
            spiclk(0);
            mosi(((data & 0x8000) ? 1 : 0));
            //printk("data=0x%x\r\n",data);
            //printk("0x%x\r\n", (data& 0x8000));
            data <<= 1;
            //while(j++ < 1);//300ns
            spiclk(1);
   }        

   //s3c2410_gpio_cfgpin(S3C2410_GPC6,S3C2410_GPIO_INPUT);
   rGPCCON = (rGPCCON & ~(3 << 12));
   for(i = 0; i < 11; i++)//送完地址及读标识位后,将数据口置为输入,再读取低11位寄存器值
   {
            spiclk(0);
            spiclk(1);
            //recv |=(s3c2410_gpio_getpin(S3C2410_GPC6) ? 1 : 0);
            recv |= ((rGPCDAT & (1 <<6)) ? 1 : 0);
            recv <<=1;    // 读入的11位寄存器值放到recv变量里面
   }
   recv >>= 1;//上面的i=10时,recv再向左移动一位,一起移了12次,多了一次,故这里返回去一次
   *cmd = 0;
   *cmd |= (recv & 0x7ff);

   Uart_Printf("read address = %d,data =0x%x\n",address,*cmd);// lqm added.
}

// 写寄存器
static voidspi_send(unsigned short data)
{
   int i;
   //int j;

   //no delay. a cycle is 800ns
   for(i = 0; i < 16; i++){
            spiclk(0);
            mosi(((data & 0x8000) ? 1 : 0));
            //printk("data=0x%x\r\n",data);
            //printf("0x%x\r\n", (data& 0x8000));
            data <<= 1;
            //while(j++ < 1);//300ns
            spiclk(1);
   }
}

// 这个函数SPI通信时需要延时!lqm.
// D11 = 0 表示写,D11 = 1 表示读.
//D[12:15]:address. D[0:10]:data
voida070vw04_cmd(unsigned short *cmd)
{
   unsigned short c = *cmd;


   //disable vsync
   //writel(readl(S3C2410_LCDCON1) & ~(0x1),S3C2410_LCDCON1);
//          rLCDCON1 &= ~1; //yuyt 2008.9.3
   spi_send_init();

   nss(0);
   if(c & 0x0800)// D11 = 0 表示写,D11 = 1 表示读.
            a070vw04_recv(cmd);
   else
            spi_send(c);
   nss(1);
   //udelay(1);//Tcv=1us??

   //enable vsync
   //writel(readl(S3C2410_LCDCON1) | (0x1),S3C2410_LCDCON1);
   //rLCDCON1 |= 1;
}
上述程序的时序严格按照屏手册的读写时序执行。其中数据总线为16位,D[15:12]为地址,D[10:0]为数据,D11为读写标识位,高电平表示读操作,低电平表示写操作。
5):Uboot中的NandFlash文件分区
文件分区的结构体数组如下:
//可更改删除分区,分区名字不可改
static structPartition NandPart[] = {
   //{0,             0x00020000, "boot"},               //128K one block
   {0,               0x00010000, "boot"},               //uboot:64K = 4blocks.block0~block3. block4:TOC
   {0x00014000, 0x00020000, "eboot"},              //eboot:128K= 8blocks. block5~block12.block13:MBR.block14,15,16保留
   //{0x00020000, 0x00060000, "bootParam"},     //384K three blocks
   {0x00040000, 0x00060000,"bootParam"},
   {0x000a0000, 0x00100000, "pic"},                 //1M
   //{0x00180000, 0x00080000,"eboot"},            //512K
   {0x001a0000, 0x03d00000, "wince"},              //???
   {0,                        0         , 0}
};
这里flash采用K9F1208U0M,一个block占用16KB,一个page占用512B,即一个block占用32个page。
从上面的配置表可以看出,整个nand flash分为七个部分:
uboot size = 0x00010000 =64KB = 4 blocks;//block0~block3
TOC size = 1 block;//block4
Eboot size =  0x00020000 = 128KB = 8 blocks;//block5~block12
MBR size = 1 block;//block13,预留block14,15,16,以防出现坏道。
bootParam size = 0x00060000 =384KB  = 24 blocks;
pic size = 0x00100000 = 1MB;
NK size = 0x03d00000 = 61MB;
以上分区为Uboot分区,具体参数,如NK大小,pic大小等还有待调节,另外还需要从NK中分出一部分给用户使用。
6):NandFlash程序分析
K390采用K9F1208,每个block占32pages,每个page占用(512+16)Byte,这两个参数是NandFlash程序的关键。
Nand的读,写,擦除都是以页为单位的,读页函数如下:
//addr = pageaddress
void ReadPage(U32addr, U8 *buf)
{
   U16 i;

   NFChipEn();
   WrNFCmd(READCMD0);
   WrNFAddr(0);
   //WrNFAddr(0);
   WrNFAddr(addr);
   WrNFAddr(addr>>8);

   if(NandAddr)
            WrNFAddr(addr>>16);

   //WrNFCmd(READCMD1);
   InitEcc();
   WaitNFBusy();
   for(i=0; i<512; i++)
            buf = RdNFDat();
   NFChipDs();
}
这是一个典型的nand读函数,程序首先使能nand控制器,再发读命令,写页地址,初始化ECC校验,等待nand空闲,再通过一个for循环,将所读取的页的数据读到数组buf[]中。可以看到这里循环了512次,这是因为一个page占用512Bytes。
Nand擦除函数如下:
// 按块擦除,故必须为一个block中多少个page的整数倍
U32 EraseBlock(U32addr)
{
   U8 stat;

   addr &= ~0x1f;// K9F1208:1 block = 32Page
   //addr &= ~0x3f;// K9F1G08:1 block = 64Page

   /*先检测是否为坏块,若是坏块的话则不再擦除,厂家标记的坏块不要动  pht 090422*/
/* // lqm maskedfor debug.shoud be recovered later.10-04-11
   if(CheckBadBlk(addr))
            return 1;
*/
   NFChipEn();
   WrNFCmd(ERASECMD0);
   WrNFAddr(addr);
   WrNFAddr(addr>>8);

   if(NandAddr)
            WrNFAddr(addr>>16);

   WrNFCmd(ERASECMD1);                  
   stat = WaitNFBusy();
   NFChipDs();


   putch('.');
   //printf("Erase block 0x%x %s\n",addr, stat?"fail":"ok");

   return stat;
}
该函数的传入参数为要擦除的page的起始数,即第几个page。而nand是按块擦除的,故这个起始数必须是每一块block的第一个page,这样程序开头就有这么一句:
addr&=~0x1f;
因为1block = 32pages,故每个block的第一个page必定会被32整除。如果换用K9F1G08,1block=64pages,这里就不是0x1f,而是0x3f了。
紧接着就是使能nand,发地址,发擦除命令。
写页函数如下:
static U32WritePage(U32 addr, U8 *buf)
{
   U32 i, mecc;
   U8 stat, tmp[7];

   NFChipEn();
   WrNFCmd(PROGCMD0);
   WrNFAddr(0);
   WrNFAddr(addr);
   WrNFAddr(addr>>8);
   if(NandAddr)
            WrNFAddr(addr>>16);
   InitEcc();      //resetmecc and secc
   MEccUnlock();
   for(i=0; i<512; i++) //写512Byte的数据,即1个Page的数据
            WrNFDat(buf);
   MEccLock();
   mecc = RdNFMEcc();
   tmp[0] = mecc&0xff;
    tmp[1] = (mecc>>8)&0xff;
    tmp[2] = (mecc>>16)&0xff;
    tmp[3] = (mecc>>24)&0xff;
    tmp[5] = 0xff;     //mark good block
    SEccUnlock();
   WrNFDat(tmp[0]);
   WrNFDat(tmp[1]);
   WrNFDat(tmp[2]);
   WrNFDat(tmp[3]);
   SEccLock();
   WrNFDat(tmp[4]);
   WrNFDat(tmp[5]);
   WrNFCmd(PROGCMD1);
   stat = WaitNFBusy();
   NFChipDs();
#ifdef    WR_BAD_BLK_TEST
   if((addr&0xff)==0x17) stat = 1;    //just for test bad block
#endif
   if(stat)
            printf("Write nand flash 0x%xfail\n", addr);
   else
   {        
            U8 RdDat[512];
            ReadPage(addr, RdDat);     //从NAND中读取刚写进去的一个page,并和DRAM中的数据比较     
            for(i=0; i<512; i++) //相同则表示写正确,否则提示校验数据错误!
                     if(RdDat!=buf)
                     {
                               printf("Checkdata at page 0x%x, offset 0x%x fail\n", addr, i);
                               stat = 1;
                               break;
                     }
   }
   return stat;    //返回0表示写成功,否则表示写失败!
}
对于写的时序按照nand的手册即可,这里要说明的是,在写函数里面最后调用了读函数,而且将读出来的数据和要写进去的数据进行了比较,如果相同则表示写成功,否则失败,这样可以确保写操作的可靠性。
第一个for循环在写命令下将函数的传入参数写进flash,在第二个for循环的前一句,即是将刚写进去的数据读出来,然后和传入参数进行比较,不同则提示写flash失败。
由于给flash进行了分区,那么就有必要针对不同的分区进行写操作。这些操作都是通过WrFileToNF()函数完成的。具体代码如下:
voidWrFileToNF(void)
{
   int i ,size, skip_blks;
   U32 ram_addr;
   struct Partition *nf_part;
   fs_yaffs=0;
   nf_part = NandSelPart("write");
   if(!nf_part)
            return;
   //StartPage =nf_part->offset>>11;//K9F1G08:1page = 2K = 2^11
   StartPage =nf_part->offset>>9;//K9F1208:1page = 512B = 2^9
   //BlockCnt = nf_part->size>>17;//K9F1G08:1block = 128K = 2^17
   BlockCnt = nf_part->size>>14;//K9F1208:1block = 16K = 2^14
// printf("\n   飞凌嵌入式      www.witech.com.cn\n");
   if(!strcmp(nf_part->name,"wince"))//strcmp串比较不管大小写
   {
            puts("\nThe 'wince' partitionis reserved for wince. please use eboot\n");
            return;
   }
   printf("\nNow download and write nandflash part [ %s ] \n",nf_part->name);
   printf("press [USB Port-->transmit]to choose the file \n");
   WaitDownload();
   if(downloadFileSize>nf_part->size)
   {
            puts("Download file size ismore large than selected partition size!!!\n");
            return;
   }
   //if(!strcmp(nf_part->name,"wince"))//strcmp串比较不管大小写
            //wince_rewrite();
   if(!strcmp(nf_part->name,"fs_yaffs"))
            fs_yaffs=2;
   printf("Now write nand flash page 0x%xfrom ram address 0x%x, filesize = %d\n", StartPage, downloadAddress,downloadFileSize);
   skip_blks = 0;
   ram_addr = downloadAddress;
   //size = downloadFileSize-10;
   size = downloadFileSize;// 这里为什么要-10???????????????????? lqm.
   for(i=0; size>0; )     {
            if(!(i&0x1f))
            {
                     if(EraseBlock(i+StartPage)) //一次擦除一个page.擦完后i加32,即接着下一个page
                     {
                               /*标记坏块并跳过改块*/
                               nf_part->size-= 32<<9;     //partition availablesize - 1 block size
                               if(downloadFileSize>nf_part->size)
                               {
                                        puts("Programnand flash fail\n");
                                        return;
                               }
                               MarkBadBlk(i+StartPage);
                               printf("bpage%x\n",i+StartPage);
                               add_bpage(i+StartPage);
                               skip_blks++;                             
                               i += 32;                                   
                               continue;
                     }
            }
            if(fs_yaffs==2)
            {
                     //i+=64;
                     fs_yaffs=1;
                     continue;
            }
            if(WritePage(i+StartPage, (U8*)ram_addr))
            {
                     ram_addr -=(i&0x1f)<<9;//i pages size
                     size +=(i&0x1f)<<9;
                     if(fs_yaffs==1)
                     {
                               ram_addr-=(i&0x1f)*32;
                               size+=(i&0x1f)*32;                           
                     }
                     i &= ~0x1f;                              
                     nf_part->size -=32<<9;     //partition availablesize - 1 block size
                     if(downloadFileSize>nf_part->size)
                     {
                               puts("Programnand flash fail\n");
                               return;
                     }                          
                     MarkBadBlk(i+StartPage);
                     printf("bpage%x\n",i+StartPage);
                     add_bpage(i+StartPage);
                     skip_blks++;                  
                     i += 32;                          
                     continue;
            }
            ram_addr += 512;
            size -= 512;
            if(fs_yaffs==1)
            {
                     ram_addr +=32;
                     size -=32;
            }
            i++;
   }
   fs_yaffs=0;
   puts("Program nand flash partitionsuccess\n");
   if(skip_blks)
   {
            printf("Skiped %d badblock(s)\n", skip_blks);
            save_params();
            //cpy_bpage();
            cpy_bpage();// 这里到底需不需要屏蔽?是起什么作用的?lqm.
   }
}
函数的开始通过交互界面选择要写入的分区,并读到结构体nf_part中,然后通过该结构体信息计算出起始页以及block的个数,具体由前面的分区表信息得来。这里假设我们要烈军属Eboot,从前面的分区信息可以看到eboot的起始地址为0x00014000,大小为0x00020000,由于1page=512Bytes,那么起始页
StartPage=0x00014000/512=0x00014000>>9=160
即eboot的起始页为第160页,即第160/32=5个block。
1block=16KB,那么所占用的block数
Blockcnt=0x00020000/16KB=0x00020000>>14=8
即给eboot分配了8个blocks的空间。
其他各分区计算方法相同。
得到起始页和占用大小后,通过对应类型进行相关编程操作,最终调用上面的擦除,读写函数完成写flash。
7):Uboot引导WINCE函数分析
在主交互界面,有一个boot WINCE的选项。通过代码看到,选择该选择项,会调用NandLoadRun_wince()函数。这个函数首先初始化nand,再调用LoadRunWince()函数。LoadRunWince()函数代码如下:
static voidLoadRunWince(void)
{
   U32 i, ram_addr;
   int size;
   struct Partition *nf_part;
   U32 EBOOT_RUN_ADDR = 0x30038000;
   U32 EBOOT_SIZE  = 0x20000;//lqm changed for K390.10-04-19//0x40000;
   //MemoryTest();
   printf("Load eboot...\n");   
   nf_part = NandSelPart_2("eboot");         
   if(!nf_part)
            return;
   StartPage = nf_part->offset>>9;
   //size = nf_part->size;
   //StartPage = NandPart[5].offset>>11;     //part 2,3...
   size =EBOOT_SIZE;//NandPart[boot_params.root_sel.val].size;
   ram_addr = EBOOT_RUN_ADDR;
   for(i=0; size>0; ) {
            if(!(i&0x1f)) {
                     if(CheckBadBlk(i+StartPage)){
                               printf("Skippedbad block at 0x%x\n", i+StartPage);
                               i += 32;
                               //size -=32<<9;
                               continue;
                     }
            }
            ReadPage((i+StartPage), (U8*)ram_addr);//将eboot读入0x30038000
            i++;
            size -= 512;//每读一个page,总大小-512,DRAM地址增512,当size<=0表示读完
            ram_addr += 512;
   }
   printf("run 0x%08x...\n",EBOOT_RUN_ADDR);
   //RelocateNKBIN(buf, &buf, &i,&boot_params.run_addr.val);
   call_linux(0, 0, EBOOT_RUN_ADDR);
}
可以看到,这个函数并不是引导WINCE,而是引导Eboot。函数的开始定义了将Eboot导入的DRAM地址,以及要导入的Eboot的大小。这里Eboot的大小要和前面的分区大小对应,避免意想不到的错误。紧接着计算出eboot的起始页和大小,再调用ReadPage函数,将eboot数据读到DRAM,最后通过call_linux函数跳转到DRAM执行Eboot。
整个Uboot到这里也就完成了,下面介绍程序进入Eboot后的工作。
Eboot
三星从WINCE5.0开始,其Eboot的风格就差不多定型了,到后来的2450,6410,C100都是大同小异。下面详细介绍整个Eboot的工作流程以及基于K390生产需要更改的部分。
Bootloader所存放的路径如下:
D:\WINCE500\PLATFORM\SMDK2440A\Src\Bootloader_1208
在该文件夹下,有两个子文件夹,一个Eboot文件夹,一个Stepldr文件夹。第二个文件夹存放的Nboot代码,这里我们用Uboot代替了,我们只关心Eboot文件夹下的代码。
2.2.1 Eboot功能
1):通过USB更新WINCE系统;
2):系统调试时下载NK到DRAM运行;
3):Eboot引导系统延时时间配置,通过在指定延时时间内按下空格键进入交互界面,否则引导进入系统;
4):文件系统分区。
    事实上Eboot还有一个重要的功能,就是通过以太网下载映像,但是由于USB的通用性以及不可替代的优势,真正开发时以太网反而用得少了,这里就不例举了。
2.2.2 Eboot框架
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtmlclip1/01/clip_image001.gif
上图为Eboot的总体框架,总体和Uboot相似,所不同的是,在5S倒计时到,或是没有2,5,0组合按键按下时,将引导WINCE系统,而不是Eboot。
Eboot功能实现的关键都在交互界面所涵盖的函数里面,下面将分块详细说明。
2.2.3 Eboot程序分析
Uboot引导Eboot到DRAM后,在DRAM中执行的第一条指令存放在Eboot文件夹的startup.s里面。由于这段代码和Uboot重复,这里不详细介绍。汇编程序初始化一系列参数之后,跳转到main.c中的main()函数,其代码如下:
void main(void)
{
    // Clear LEDs.
    //   
    OEMWriteDebugLED(0, 0xf);

    // Common boot loader (blcommon) mainroutine.
    //   
    BootloaderMain();

    // Should never get here.
    //
    SpinForever();
}
可以看到,关键在BootloaderMain()函数。代码如下:
voidBootloaderMain (void)
{
    DWORD dwAction;   
    DWORD dwpToc = 0;
    DWORD dwImageStart = 0, dwImageLength = 0,dwLaunchAddr = 0;
    BOOL bDownloaded = FALSE;

    // relocate globals to RAM
    if (!KernelRelocate (pTOC))
    {
        // spin forever
        HALT (BLERR_KERNELRELOCATE);
    }

    // (1) Init debug support. We can useOEMWriteDebugString afterward.
    if (!OEMDebugInit ())
    {
        // spin forever
        HALT (BLERR_DBGINIT);
    }

    // output banner
    EdbgOutputDebugString (NKSignon,CURRENT_VERSION_MAJOR, CURRENT_VERSION_MINOR);

    // (3) initialize platform (clock, drivers,transports, etc)
    if (!OEMPlatformInit ())
    {
        // spin forever
        HALT (BLERR_PLATINIT);
    }

    // system ready, preparing for download
    EdbgOutputDebugString ("Systemready!\r\nPreparing for download...\r\n");

    // (4) call OEM specific pre-downloadfunction
    switch (dwAction = OEMPreDownload ())
    {
    case BL_DOWNLOAD:
        // (5) download image
        if (!DownloadImage (&dwImageStart,&dwImageLength, &dwLaunchAddr))
        {
            // error already reported inDownloadImage
            SPIN_FOREVER;
        }
        bDownloaded = TRUE;

        // Check for pTOC signature("CECE") here, after image in place
        if (*(LPDWORD) OEMMapMemAddr(dwImageStart, dwImageStart + ROM_SIGNATURE_OFFSET) == ROM_SIGNATURE)
        {
            dwpToc = *(LPDWORD) OEMMapMemAddr(dwImageStart, dwImageStart + ROM_SIGNATURE_OFFSET + sizeof(ULONG));
            // need to map the content againsince the pointer is going to be in a fixup address
            dwpToc = (DWORD) OEMMapMemAddr (dwImageStart,dwpToc + g_dwROMOffset);

            EdbgOutputDebugString ("ROMHDRat Address %Xh\r\n", dwImageStart + ROM_SIGNATURE_OFFSET + sizeof(DWORD)); // right after signature
        }

        // fall through
    case BL_JUMP:
        // Before jumping to the image,optionally check the image signature.
        // NOTE: if we haven't downloaded theimage by now, we assume that it'll be loaded from local storage in OEMLaunch(or it
        // already resides in RAM from anearlier download), and in this case, the image start address might be 0.  This means
        // that the image signature routinewill need to find the image in storage or in RAM to validate it.  Since the OEM"s
        // OEMLaunch function will need to dothis anyways, we trust that it's within their abilities to do it here.
        //
        if (g_bBINDownload &&g_pOEMCheckSignature)
        {
            if(!g_pOEMCheckSignature(dwImageStart, g_dwROMOffset, dwLaunchAddr, bDownloaded))
                HALT(BLERR_CAT_SIGNATURE);
        }
        // (5) final call to launch the image.never returned
        OEMLaunch (dwImageStart, dwImageLength,dwLaunchAddr, (const ROMHDR *)dwpToc);
        // should never return
        // fall through
    default:
        // ERROR! spin forever
        HALT (BLERR_INVALIDCMD);
    }
}
这个函数是整个Eboot的大的框架,首先将要使用的全局变量放在RAM中,初始化调试串口,再初始化硬件平台,接下来通过函数OEMPreDownload()的返回值,决定下载映像或是加载映像。
上面调用三大重要的函数完成相关工作:
OEMPlatformInit();
DownloadImage();
OEMLaunch();
他们分别完成初始化硬件平台、下载映像,加载映像等工作。
OEMPlatformInit()函数代码如下:
BOOLOEMPlatformInit(void)
{
    ULONG BootDelay;
    UINT8 KeySelect;
    UINT32 dwStartTime, dwPrevTime, dwCurrTime;
    BOOLEAN bResult = FALSE;

   //---->add by phantom
    //DWORD i;
    //SectorInfo si;
   //<----

    OALMSG(OAL_FUNC,(TEXT("+OEMPlatformInit.\r\n")));

    EdbgOutputDebugString("MicrosoftWindows CE Bootloader for the Samsung SMDK2440 Version %d.%d Built%s\r\n\r\n",
                          EBOOT_VERSION_MAJOR,EBOOT_VERSION_MINOR, __DATE__);

    // Initialize the display.
    //
    InitDisplay();// lqm opened.10-04-13

    // Initialize the BSP args structure.
    //
    memset(pBSPArgs, 0, sizeof(BSP_ARGS));
    pBSPArgs->header.signature       = OAL_ARGS_SIGNATURE;
    pBSPArgs->header.oalVersion      = OAL_ARGS_VERSION;
    pBSPArgs->header.bspVersion      = BSP_ARGS_VERSION;
    pBSPArgs->kitl.flags             = OAL_KITL_FLAGS_ENABLED |OAL_KITL_FLAGS_VMINI;
    pBSPArgs->kitl.devLoc.IfcType    = Internal;
    pBSPArgs->kitl.devLoc.BusNumber  = 0;
    //pBSPArgs->kitl.devLoc.LogicalLoc =BSP_BASE_REG_PA_CS8900A_IOBASE;
    pBSPArgs->kitl.devLoc.LogicalLoc =BSP_BASE_REG_PA_DM9000A_IOBASE;

   // For USB Download functon
   //
   if (!InitUSB())
   {
            OALMSG(1,(TEXT("OEMPlatformInit: Failed to initialize USB.\r\n")));
            return(FALSE);
   }
   Isr_Init();
    // This should not change unless reservedblocks are added/removed;
    // made global to do the calc only once.
    g_dwImageStartBlock = IMAGE_START_BLOCK;

    // Try to initialize the boot media blockdriver and BinFS partition.
    //
    if ( !BP_Init((LPBYTE)BINFS_RAM_START,BINFS_RAM_LENGTH, NULL, NULL, NULL) )
    {
        OALMSG(OAL_WARN, (TEXT("WARNING:OEMPlatformInit failed to initialize Boot Media.\r\n")));
        g_bBootMediaExist = FALSE;
    }
    else
        g_bBootMediaExist = TRUE;

    // Try to retrieve TOC (and Boot config)from boot media
    //
    if ( !TOC_Read( ) )
    {
        // use default settings
        TOC_Init(DEFAULT_IMAGE_DESCRIPTOR,(IMAGE_TYPE_RAMIMAGE), 0, 0, 0);
    }

    // Display boot message - user can halt theautoboot by pressing any key on the serial terminal emulator.
    //
    BootDelay = g_pBootCfg->BootDelay;


   //---->add by phantom
   /*
   //用JTAG重新烧写EBOOT后可能会将其标记为好块

    // to keep bootpart off of our reservedblocks we must mark it as bad, reserved & read-only
    si.bOEMReserved = OEM_BLOCK_RESERVED |OEM_BLOCK_READONLY;//?? 与FMD.CPP中不一致 应该取反? ->by phantom
    si.bBadBlock    = BADBLOCKMARK;//这里必须标记为坏块,否则BP_OpenPartition中调用BP_LowLevelFormat时会擦掉 ->by phantom
    si.dwReserved1  = 0xffffffff;
    si.wReserved2   = 0xffff;

    for (i = 0; i < IMAGE_START_SECTOR; i++){
        FMD_WriteSector(i, NULL, &si, 1);
    }
   */
   //<----


    if (g_pBootCfg->ConfigFlags &BOOT_TYPE_DIRECT)
    {
        OALMSG(TRUE, (TEXT("Press [ENTER]to launch image stored on boot media, or [SPACE] to enter bootmonitor.\r\n")));
        OALMSG(TRUE, (TEXT("\r\nInitiatingimage launch in %d seconds. "),BootDelay--));

    }
    else
    {
        OALMSG(TRUE, (TEXT("Press [ENTER]to download image stored on boot media, or [SPACE] to enter boot monitor.\r\n")));
        OALMSG(TRUE, (TEXT("\r\nInitiatingimage download in %d seconds. "),BootDelay--));
    }

    dwStartTime = OEMEthGetSecs();
    dwPrevTime = dwStartTime;
    dwCurrTime = dwStartTime;
    KeySelect  = 0;

    // Allow the user to break into thebootloader menu.
    //
    while((dwCurrTime - dwStartTime) <g_pBootCfg->BootDelay)
    {
        KeySelect = OEMReadDebugByte();
        if ((KeySelect == 0x20) || (KeySelect== 0x0d))
            break;
        dwCurrTime = OEMEthGetSecs();

        if (dwCurrTime > dwPrevTime)
        {
            int i, j;

            // 1 Second has elapsed - updatethe countdown timer.
            dwPrevTime = dwCurrTime;
            if (BootDelay < 9)
                i = 11;
            else if (BootDelay < 99)
                i = 12;
            else if (BootDelay < 999)
                i = 13;

            for(j = 0; j < i; j++)
                OEMWriteDebugByte((BYTE)0x08);// print back space
            EdbgOutputDebugString ( "%dseconds. ", BootDelay--);
        }
    }
   OALMSG(OAL_INFO, (TEXT("\r\n")));

    // Boot or enter bootloader menu.
    //
    switch(KeySelect)
    {
    case 0x20: // Boot menu.
        g_bDownloadImage =MainMenu(g_pBootCfg);
        break;
    case 0x00: // Fall through if no keys werepressed -or-
    case 0x0d: // the user cancelled thecountdown.
    default:
        if (g_pBootCfg->ConfigFlags &BOOT_TYPE_DIRECT)
        {
                     OALMSG(TRUE,(TEXT("\r\nLaunching image from boot media ... \r\n")));
            g_bDownloadImage = FALSE;
        }
        else
        {
                     OALMSG(TRUE,(TEXT("\r\nStarting auto-download ... \r\n")));
            g_bDownloadImage = TRUE;
        }
        break;
    }

    if ( !g_bDownloadImage )
    {
        // User doesn't want to download image- load it from the boot media.
        // We could read an entire nk.bin ornk.nb0 into ram and jump.
        if ( !VALID_TOC(g_pTOC) ) {
                     OALMSG(OAL_ERROR,(TEXT("OEMPlatformInit: ERROR_INVALID_TOC, can not autoboot.\r\n")));
            return FALSE;
        }
        switch (g_ImageType) {
                caseIMAGE_TYPE_STEPLDR:
                               OALMSG(TRUE,(TEXT("Don't support launch STEPLDR.bin\r\n")));
                break;

            case IMAGE_TYPE_LOADER:
                               OALMSG(TRUE,(TEXT("Don't support launch EBOOT.bin\r\n")));
                break;

            case IMAGE_TYPE_RAMIMAGE:
                               OALMSG(TRUE,(TEXT("OEMPlatformInit: IMAGE_TYPE_RAMIMAGE\r\n")));
                if ( !ReadOSImageFromBootMedia() )
                {
                    OALMSG(OAL_ERROR, (TEXT("OEMPlatformInitERROR: Failed to load kernel region into RAM.\r\n")));
                    return FALSE;
                }
                               break;

            default:
                               OALMSG(OAL_ERROR,(TEXT("OEMPlatformInit ERROR: unknown image type: 0x%x \r\n"),g_ImageType));
                return FALSE;
        }
    }

    // Configure Ethernet controller.
    //
   if ( g_bUSBDownload == FALSE )
   {
            if (!InitEthDevice(g_pBootCfg))
         {
                OALMSG(OAL_ERROR,(TEXT("ERROR: OEMPlatformInit: Failed to initialize Ethernetcontroller.\r\n")));
                gotoCleanUp;
        }
   }

    bResult = TRUE;

CleanUp:

    OALMSG(OAL_FUNC,(TEXT("_OEMPlatformInit.\r\n")));
    return(bResult);
}



作者: armeasy    时间: 2012-7-19 11:35
这个函数首先初始化LCD显示,初始化USB,中断,初始化flash,然后从nand中读取TOC,读取成功后再进入5秒倒计时,随后根据用户操作或是缺省配置进入交互界面或是引导系统。
值得注意的是,TOC这个参数相当的重要。从字面上讲,TOC即table of context,它里面存放了Eboot和NK的版本号,标识号,字符串标识号,映像类型,所占用的页长度,引导地址,跳转地址,起始页地址等信息,给个打印信息就一清二楚了:
TOC {
dwSignature:0x434F544E
BootCfg {
  ConfigFlags: 0x830
  BootDelay: 0x5
  ImageIndex: 1
  IP: 192.168.1.115
  MAC Address: 00:11:22:33:44:55
  Port: 0.0.0.0
  SubnetMask: 255.255.255.0
}
ID[0] {
  dwVersion: 0x20004
  dwSignature: 0x45424F54
  String: 'eboot.nb0'
  dwImageType: 0x2
  dwTtlSectors: 0x200
  dwLoadAddress: 0x80038000
  dwJumpAddress: 0x80038000
  dwStoreOffset: 0x0
  sgList[0].dwSector: 0xA0
  sgList[0].dwLength: 0x200
}
ID[1] {
  dwVersion: 0x1
  dwSignature: 0x43465348
  String: ''
  dwImageType: 0x2
  dwTtlSectors: 0x1119D
  dwLoadAddress: 0x80200000
  dwJumpAddress: 0x8022C4C8
  dwStoreOffset: 0x0
  sgList[0].dwSector: 0x360
  sgList[0].dwLength: 0x1119D
}
chainInfo.dwLoadAddress:0X00000000
chainInfo.dwFlashAddress:0X00000000
chainInfo.dwLength:0X00000000
}
如果TOC出错,则无法正常引导系统。从上面的程序可以看出,在switch语句里面,如果检测到0x20,即空格键,MainMenu()函数会被调用,这个函数会打印主交互界面的信息,具体执行什么任务,需要用户指派。打印信息如下:
Ethernet BootLoader Configuration:

0) IP address:192.168.1.115
1) Subnet mask:255.255.255.0
2) DHCP: Disabled
3) Boot delay: 5seconds
4) Reset tofactory default configuration
5) Startup image:LAUNCH EXISTING
6) Program diskimage into SmartMedia card: Enabled
7) Program CS8900MAC address (00:11:22:33:44:55)
8) KernelDebugger: DISABLED
9) Format BootMedia for BinFS
F) Low-levelformat the Smart Media card
D) Download imagenow
L) LAUNCH existingBoot Media image
U) DOWNLOAD imagenow(USB)
R) ReadConfiguration
W) WriteConfiguration Right Now

Enter yourselection:
3):配置启动延时时间;
4):复位TOC里面信息到出厂设置;
5):运行系统和下载系统选择;
6):下载系统到nand和SDRAM选择;
9):格式化nand为BinFS格式;
F):低格nand flash;
L):运行nand中存在的系统;
U):通过USB下载映像;
R):读取TOC信息;
W):保存配置的参数信息。
这里我们比较关心一块新的flash,我们需要对他的操作。在Uboot中,我们保留了Uboot,TOC,Eboot,MBR,BootParam,pic,NK七段,其中在烧写Uboot,Eboot,pic以及BootParam时,已经对相应段擦写过一遍了,而TOC段,MBR段,NK段还没有任何操作。这些都会在这里下载NK时进行。下面我们逐步分析。
在上面打印信息的配置下,按U,将会通过USB下载NK到nand。查看代码,当按U后,程序给bConfigChanged ,g_bUSBDownload,bDownload赋1,然后执行TOC_Write()函数,进入这个函数可以发现,先擦除TOC所存放的块,这里存放TOC的位置要严格和Uboot里面预定义的相同,我们把它放在block4。之后将TOC信息写入块。g_bDownloadImage通过获取MainMenu()的返回值,即bDownload的值为1,这时OEMPlatformInit()函数已经运行完毕,再回过来看BootloaderMain()函数,该函数在执行完OEMPlatformInit()函数后,通过调用OEMPreDownload()函数决定下载映像或是引导映像。我们看看OEMPreDownload()函数:
DWORDOEMPreDownload(void)
{
    BOOL bGotJump = FALSE;
    DWORDdwDHCPLeaseTime = 0;
    PDWORD pdwDHCPLeaseTime =&dwDHCPLeaseTime;
    DWORD dwBootFlags = 0;

    OALMSG(OAL_FUNC,(TEXT("+OEMPreDownload.\r\n")));

    // Create device name based on Ethernetaddress (this is how Platform Builder identifies this device).
    //
    OALKitlCreateName(BSP_DEVICE_PREFIX,pBSPArgs->kitl.mac, pBSPArgs->deviceId);
    OALMSG(OAL_INFO, (L"INFO: *** DeviceName '%hs' ***\r\n", pBSPArgs->deviceId));

   if ( g_bUSBDownload == FALSE )
   {
            // If the user wants to use a staticIP address, don't request an address
            // from a DHCP server.  This is done by passing in a NULL for theDHCP
            // lease time variable.  If user specified a static IP address, use it(don't use DHCP).
            //
            if (!(g_pBootCfg->ConfigFlags& CONFIG_FLAGS_DHCP))
            {
                     // Static IP address.
                     pBSPArgs->kitl.ipAddress  = g_pBootCfg->EdbgAddr.dwIP;
                     pBSPArgs->kitl.ipMask     = g_pBootCfg->SubnetMask;
                     pBSPArgs->kitl.flags     &= ~OAL_KITL_FLAGS_DHCP;
                     pdwDHCPLeaseTime = NULL;
                     OALMSG(OAL_INFO, (TEXT("INFO:Using static IP address %s.\r\n"),inet_ntoa(pBSPArgs->kitl.ipAddress)));
                     OALMSG(OAL_INFO,(TEXT("INFO: Using subnet mask %s.\r\n"),       inet_ntoa(pBSPArgs->kitl.ipMask)));
            }
            else
            {
                     pBSPArgs->kitl.ipAddress= 0;
                     pBSPArgs->kitl.ipMask    = 0;
            }

            if ( !g_bDownloadImage)
            {
                     return(BL_JUMP);
            }

            // Initialize the the TFTPtransport.
            //
            g_DeviceAddr.dwIP =pBSPArgs->kitl.ipAddress;
            memcpy(g_DeviceAddr.wMAC,pBSPArgs->kitl.mac, (3 * sizeof(UINT16)));
            g_DeviceAddr.wPort = 0;

            if(!EbootInitEtherTransport(&g_DeviceAddr,
                                                                              &pBSPArgs->kitl.ipMask,
                                                                              &bGotJump,
                                                                              pdwDHCPLeaseTime,
                                                                              EBOOT_VERSION_MAJOR,
                                                                              EBOOT_VERSION_MINOR,
                                                                              BSP_DEVICE_PREFIX,
                                                                              pBSPArgs->deviceId,
                                                                              EDBG_CPU_ARM720,
                                                                              dwBootFlags))
            {
                     OALMSG(OAL_ERROR,(TEXT("ERROR: OEMPreDownload: Failed to initialize Ethernetconnection.\r\n")));
                     return(BL_ERROR);
            }


            // If the user wanted a DHCPaddress, we presumably have it now - save it for the OS to use.
            //
            if (g_pBootCfg->ConfigFlags &CONFIG_FLAGS_DHCP)
            {
                     // DHCP address.
                     pBSPArgs->kitl.ipAddress  = g_DeviceAddr.dwIP;
                     pBSPArgs->kitl.flags     |= OAL_KITL_FLAGS_DHCP;
            }

            OALMSG(OAL_FUNC,(TEXT("_OEMPreDownload.\r\n")));
   }
   else
   {
            OALMSG(TRUE, (TEXT("Please sendthe Image through USB.\r\n")));
   }

    return(bGotJump ? BL_JUMP : BL_DOWNLOAD);
}
由于之前在OEMPlatformInit()函数中,g_bUSBDownload的值已经被赋为1,所以上面程序执行到最后,bGotJump=0,也就是return BL_DOWNLOAD。那么在BootloaderMain()函数中,将会调用DownloadImage()函数下载映像。
在DownloadImage()函数中,首先从USB获得的BIN文件中读取BIN文件的前7个字节的数据,对于标准的BIN文件,前7个字节的数据一定是“B000FF\x0A”,接下来再读BIN文件的起始地址和长度,这两个数据很重要,读出来的数据会被复制到结构体g_DownloadManifest中,然后就是将BIN文件剩下来的一条条记录读到DRAM中。整个DownloadImage()函数需要对NK.bin或XIP.bin的结构相当了解,程序完全按照其结构将里面的数据一条条的解析出来。一个bin 文件在存储上是按下面的结构存储的:
     组成:标记(7)+Image开始地址(1)+Image长度(1)  
           记录0地址+记录0长+记录0校验和+记录0内容(文件内容)
           记录1地址+记录1长+记录1校验和+记录1内容(文件内容)
           ......
     最后一条记录是表示结束。
     bin 文件的头部(不包括记录)可以用下面的结构表示
  structBinFile
{
                 BYTE signature[7]; // = { ''B'', ''0'', ''0'', ''0'', ''F'', ''F'',''\a'' }
                 DWORD ImageStart
                 DWORD ImageLength
          };
有了上面的具体结构分析,DownloadImage()函数就不难理解了。这个函数执行完之后,我们再回到BootloaderMain()函数中,我们细心一点,会发现原来那个switch语句中,BL_DOWNLOAD条件的case语句执行完之后,并没有break,也就是说执行完DownloadImage()函数之后,并没有退出,而是继续执行BL_JUMP条件的case语句。该语句调用了OEMLaunch()函数,函数传入了上面读出来的映像起始地址,映像长度以及运行地址。
真正将nk.bin或XIP.bin数据写到nand中,并不是在DownloadImage()中,而是在OEMLaunch()中。其代码如下:
void OEMLaunch( DWORD dwImageStart, DWORDdwImageLength, DWORD dwLaunchAddr, const ROMHDR *pRomHdr )
{
   DWORD dwPhysLaunchAddr;
   EDBG_ADDR EshellHostAddr;
   EDBG_OS_CONFIG_DATA *pCfgData;   

   OALMSG(OAL_FUNC, (TEXT("+OEMLaunch.\r\n")));

    //If the user requested that a disk image (stored in RAM now) be written to theSmartMedia card, so it now.
    //
    if(g_bDownloadImage && (g_pBootCfg->ConfigFlags &TARGET_TYPE_NAND))
    {
       // Since this platform only supports RAM images, the image cache addressis the same as the image RAM address.
       //

       switch (g_ImageType)
       {
           case IMAGE_TYPE_STEPLDR:
                         if (!WriteRawImageToBootMedia(dwImageStart,dwImageLength, dwLaunchAddr))
                         {
                           OALMSG(OAL_ERROR,(TEXT("ERROR: OEMLaunch: Failed to store image to SmartMedia.\r\n")));
                           goto CleanUp;
                    }
                         OALMSG(TRUE, (TEXT("INFO: Step loaderimage stored to Smart Media.  PleaseReboot.  Halting...\r\n")));
                     while(1)
                     {
                           // Wait...
                     }
                    break;

           case IMAGE_TYPE_LOADER:
                                    g_pTOC->id[0].dwLoadAddress= dwImageStart;
                                    g_pTOC->id[0].dwTtlSectors= FILE_TO_SECTOR_SIZE(dwImageLength);
                         if(!WriteRawImageToBootMedia(dwImageStart, dwImageLength, dwLaunchAddr))
                         {
                           OALMSG(OAL_ERROR,(TEXT("ERROR: OEMLaunch: Failed to store image to Smart Media.\r\n")));
                           goto CleanUp;
                    }
                                    if(dwLaunchAddr && (g_pTOC->id[0].dwJumpAddress != dwLaunchAddr))
                                    {
                                             g_pTOC->id[0].dwJumpAddress= dwLaunchAddr;
                                             if( !TOC_Write() ) {
                                     EdbgOutputDebugString("***OEMLaunch ERROR: TOC_Write failed! Next boot may not load from disk ***\r\n");
                                             }
                               TOC_Print();
                                    }
                         OALMSG(TRUE, (TEXT("INFO: Ebootimage stored to Smart Media.  PleaseReboot.  Halting...\r\n")));
                         while(1)
                         {
                           // Wait...
                    }                     

                break;

           case IMAGE_TYPE_RAMIMAGE:
                                    g_pTOC->id[g_dwTocEntry].dwLoadAddress= dwImageStart;
                                    g_pTOC->id[g_dwTocEntry].dwTtlSectors= FILE_TO_SECTOR_SIZE(dwImageLength);
                         if(!WriteOSImageToBootMedia(dwImageStart, dwImageLength, dwLaunchAddr))
                         {
                           OALMSG(OAL_ERROR,(TEXT("ERROR: OEMLaunch: Failed to store image to SmartMedia.\r\n")));
                           goto CleanUp;
                    }

                                    if(dwLaunchAddr && (g_pTOC->id[g_dwTocEntry].dwJumpAddress !=dwLaunchAddr))
                                    {
                                             g_pTOC->id[g_dwTocEntry].dwJumpAddress= dwLaunchAddr;
                                             if( !TOC_Write() ) {
                                     EdbgOutputDebugString("***OEMLaunch ERROR: TOC_Write failed! Next boot may not load from disk ***\r\n");
                                             }
                               TOC_Print();
                                    }
                                    else
                                    {
                                             dwLaunchAddr=g_pTOC->id[g_dwTocEntry].dwJumpAddress;
                                             EdbgOutputDebugString("INFO:using TOC[%d] dwJumpAddress: 0x%x\r\n", g_dwTocEntry, dwLaunchAddr);
                                    }

                break;
       }
    }
   else if(g_bDownloadImage)
    {
       switch (g_ImageType)
       {
           case IMAGE_TYPE_STEPLDR:
                         OALMSG(TRUE, (TEXT("Stepldr imagecan't launch from ram.\r\n")));
                         OALMSG(TRUE, (TEXT("You shouldprogram it into flash.\r\n")));
                         SpinForever();
                                    break;
           case IMAGE_TYPE_LOADER:
                         OALMSG(TRUE, (TEXT("Eboot imagecan't launch from ram.\r\n")));
                         OALMSG(TRUE, (TEXT("You shouldprogram it into flash.\r\n")));
                         SpinForever();
                         break;
           default:
                  break;
     }
    }

   OALMSG(1, (TEXT("waitforconnect\r\n")));
    //Wait for Platform Builder to connect after the download and send us IP and portsettings for service
    //connections - also sends us KITL flags. This information is used later by the OS (KITL).
    //
    if(~g_bUSBDownload & g_bDownloadImage & g_bWaitForConnect)
    {
       memset(&EshellHostAddr, 0, sizeof(EDBG_ADDR));

       g_DeviceAddr.dwIP  =pBSPArgs->kitl.ipAddress;
       memcpy(g_DeviceAddr.wMAC, pBSPArgs->kitl.mac, (3 * sizeof(UINT16)));
       g_DeviceAddr.wPort = 0;

       if (!(pCfgData = EbootWaitForHostConnect(&g_DeviceAddr,&EshellHostAddr)))
       {
           OALMSG(OAL_ERROR, (TEXT("ERROR: OEMLaunch: EbootWaitForHostConnectfailed.\r\n")));
           goto CleanUp;
       }

       // If the user selected "passive" KITL (i.e., don't connect tothe target at boot time), set the
       // flag in the args structure so the OS image can honor it when itboots.
       //
       if (pCfgData->KitlTransport & KTS_PASSIVE_MODE)
       {
           pBSPArgs->kitl.flags |= OAL_KITL_FLAGS_PASSIVE;
       }
        }

    //If a launch address was provided, we must have downloaded the image, save theaddress in case we
    //want to jump to this image next time.  Ifno launch address was provided, retrieve the last one.
    //
        if(dwLaunchAddr && (g_pTOC->id[g_dwTocEntry].dwJumpAddress !=dwLaunchAddr))
        {
                 g_pTOC->id[g_dwTocEntry].dwJumpAddress= dwLaunchAddr;
        }
        else
        {
                 dwLaunchAddr=g_pTOC->id[g_dwTocEntry].dwJumpAddress;
                 OALMSG(OAL_INFO,(TEXT("INFO: using TOC[%d] dwJumpAddress: 0x%x\r\n"), g_dwTocEntry,dwLaunchAddr));
        }

    //Jump to downloaded image (use the physical address since we'll be turning theMMU off)...
    //
   dwPhysLaunchAddr = (DWORD)OALVAtoPA((void *)dwLaunchAddr);
   OALMSG(TRUE, (TEXT("INFO: OEMLaunch: Jumping to Physical Address0x%Xh (Virtual Address 0x%Xh)...\r\n\r\n\r\n"), dwPhysLaunchAddr,dwLaunchAddr));

    //Jump...
    //
   Launch(dwPhysLaunchAddr);


CleanUp:

   OALMSG(TRUE, (TEXT("ERROR: OEMLaunch: Halting...\r\n")));
   SpinForever();
}

在这个函数中,我们又看到了熟悉的变量g_bDownloadImage。程序通过这个变量判断是运行nand中存在的映像还是写映像到nand。当然这里是下载映像到nand了。函数中又出现了一个switch语句,里面有三个case变量,IMAGE_TYPE_STEPLDR,IMAGE_TYPE_LOADER以及IMAGE_TYPE_RAMIMAGE,分别对应steploader,eboot以及nk。前面两个我们通过uboot烧到nand了,当然也可以通过这里烧写。因此我们只关心IMAGE_TYPE_RAMIMAGE。该case语句里面,调用了WriteOSImageToBootMedia()函数,这个函数就是真正实现将系统映像文件写到nandflash的源函数!在将数据写到nand之前,该函数会进行TOC,MBR校验,分区。其代码如下:

BOOL WriteOSImageToBootMedia(DWORDdwImageStart, DWORD dwImageLength, DWORD dwLaunchAddr)
{
   BYTE nCount;
   DWORD dwNumExts;
   PXIPCHAIN_SUMMARY pChainInfo = NULL;
   EXTENSION *pExt = NULL;
   DWORD dwBINFSPartLength = 0;
   HANDLE hPart, hPartEx;
   DWORD dwStoreOffset;
   DWORD dwMaxRegionLength[BL_MAX_BIN_REGIONS] = {0};
   DWORD dwChainStart, dwChainLength;

   //  Initialize the variables
   dwChainStart = dwChainLength = 0;

   OALMSG(OAL_FUNC, (TEXT("+WriteOSImageToBootMedia\r\n")));
   OALMSG(OAL_INFO, (TEXT("+WriteOSImageToBootMedia: g_dwTocEntry =%d,ImageStart: 0x%x, ImageLength: 0x%x, LaunchAddr:0x%x\r\n"),
                            g_dwTocEntry,dwImageStart, dwImageLength, dwLaunchAddr));

    if( !g_bBootMediaExist )
    {
       OALMSG(OAL_ERROR, (TEXT("ERROR: WriteOSImageToBootMedia: devicedoesn't exist.\r\n")));
       return(FALSE);
    }

    if( !VALID_TOC(g_pTOC) )
    {
       OALMSG(OAL_WARN, (TEXT("WARN: WriteOSImageToBootMedia:INVALID_TOC\r\n")));
       if ( !TOC_Init(g_dwTocEntry, g_ImageType, dwImageStart, dwImageLength,dwLaunchAddr) )
       {
           OALMSG(OAL_ERROR, (TEXT("ERROR: INVALID_TOC\r\n")));
           return(FALSE);
       }
    }

    //Look in the kernel region's extension area for a multi-BIN extensiondescriptor.
    //This region, if found, details the number, start, and size of each BIN region.
    //
    for(nCount = 0, dwNumExts = 0 ; (nCount < g_BINRegionInfo.dwNumRegions);nCount++)
    {
       // Does this region contain nk.exe and an extension pointer?
       //
       pExt = (EXTENSION*)GetKernelExtPointer(g_BINRegionInfo.Region[nCount].dwRegionStart,
                                               g_BINRegionInfo.Region[nCount].dwRegionLength );
       if ( pExt != NULL)
       {
           // If there is an extension pointer region, walk it until the end.
           //
           while (pExt)
           {
                DWORD dwBaseAddr =g_BINRegionInfo.Region[nCount].dwRegionStart;
                pExt = (EXTENSION*)OEMMapMemAddr(dwBaseAddr, (DWORD)pExt);
                OALMSG(OAL_INFO,(TEXT("INFO: OEMLaunch: Found chain extenstion: '%s' @ 0x%x\r\n"),pExt->name, dwBaseAddr));
                if ((pExt->type == 0)&& !strcmp(pExt->name, "chain information"))
                {
                    pChainInfo =(PXIPCHAIN_SUMMARY) OEMMapMemAddr(dwBaseAddr, (DWORD)pExt->pdata);
                    dwNumExts =(pExt->length / sizeof(XIPCHAIN_SUMMARY));
                    OALMSG(OAL_INFO,(TEXT("INFO: OEMLaunch: Found 'chain information' (pChainInfo=0x%x  Extensions=0x%x).\r\n"),(DWORD)pChainInfo, dwNumExts));
                    break;
                }
                pExt = (EXTENSION*)pExt->pNextExt;
           }
       }
       else {
           //  Search for Chain region. Chainregion doesn't have the ROMSIGNATURE set
           DWORD   dwRegionStart =g_BINRegionInfo.Region[nCount].dwRegionStart;
           DWORD   dwSig = *(LPDWORD)OEMMapMemAddr(dwRegionStart, dwRegionStart + ROM_SIGNATURE_OFFSET);

           if ( dwSig != ROM_SIGNATURE) {
                // It is the chain
                dwChainStart = dwRegionStart;
                dwChainLength =g_BINRegionInfo.Region[nCount].dwRegionLength;
                OALMSG(TRUE, (TEXT("Foundthe Chain region: StartAddress: 0x%X; Length: 0x%X\n"), dwChainStart,dwChainLength));
           }
       }
    }

    //Determine how big the Total BINFS partition needs to be to store all of this.
    //
    if(pChainInfo && dwNumExts == g_BINRegionInfo.dwNumRegions)    // We're downloading all the regions in amulti-region image...
    {
       DWORD i;
       OALMSG(TRUE, (TEXT("Writing multi-regions\r\n")));

       for (nCount = 0, dwBINFSPartLength = 0 ; nCount < dwNumExts ;nCount++)
       {
           dwBINFSPartLength += (pChainInfo + nCount)->dwMaxLength;
           OALMSG(OAL_ERROR, (TEXT("BINFSPartMaxLength[%u]: 0x%x,TtlBINFSPartLength: 0x%x \r\n"),
                nCount, (pChainInfo +nCount)->dwMaxLength, dwBINFSPartLength));

           // MultiBINInfo does not store each Regions MAX length, and pChainInfois not in any particular order.
           // So, walk our MultiBINInfo matching up pChainInfo to find each regionsMAX Length
           for (i = 0; i < dwNumExts; i++) {
                if (g_BINRegionInfo.Region.dwRegionStart == (DWORD)((pChainInfo +nCount)->pvAddr) ) {
                    dwMaxRegionLength =(pChainInfo + nCount)->dwMaxLength;
                    OALMSG(TRUE,(TEXT("dwMaxRegionLength[%u]: 0x%x \r\n"), i, dwMaxRegionLength));
                    break;
                }
           }
       }
    }
   else    // A single BIN file orpotentially a multi-region update (but the partition's already been created inthis latter case).
    {
       dwBINFSPartLength = g_BINRegionInfo.Region[0].dwRegionLength;
       OALMSG(TRUE, (TEXT("Writing single region/multi-region update,dwBINFSPartLength: %u \r\n"), dwBINFSPartLength));
    }
    //Open/Create the BINFS partition where images are stored.  This partition starts immediately after theMBR on the Boot Media and its length is
    //determined by the maximum image size (or sum of all maximum sizes in amulti-region design).
    //Parameters are LOGICAL sectors.
    //
   hPart = BP_OpenPartition( (IMAGE_START_BLOCK+1)*PAGES_PER_BLOCK, // next block of MBR
                             SECTOR_TO_BLOCK_SIZE(FILE_TO_SECTOR_SIZE(dwBINFSPartLength))*PAGES_PER_BLOCK,// align to block
                              PART_BINFS,
                              TRUE,
                              PART_OPEN_ALWAYS);
    if(hPart == INVALID_HANDLE_VALUE )
    {
       OALMSG(OAL_ERROR, (TEXT("ERROR: WriteOSImageToBootMedia: Failed toopen/create partition.\r\n")));
       return(FALSE);
    }
    //Are there multiple BIN files in RAM (we may just be updating one in a multi-BINsolution)?
    //
   for (nCount = 0, dwStoreOffset = 0; nCount <g_BINRegionInfo.dwNumRegions ; nCount++)
    {
       DWORD dwRegionStart  =(DWORD)OEMMapMemAddr(0, g_BINRegionInfo.Region[nCount].dwRegionStart);
       DWORD dwRegionLength = g_BINRegionInfo.Region[nCount].dwRegionLength;
       // Media byte offset where image region is stored.
       dwStoreOffset += nCount ? dwMaxRegionLength[nCount-1] : 0;
       // Set the file pointer (byte indexing) to the correct offset for thisparticular region.
       //
       if ( !BP_SetDataPointer(hPart, dwStoreOffset) )//设置分区数据指针?指向第0个分区???
       {
           OALMSG(OAL_ERROR, (TEXT("ERROR: StoreImageToBootMedia: Failed toset data pointer in partition (offset=0x%x).\r\n"), dwStoreOffset));
           return(FALSE);
       }
       // Write the region to the BINFS partition.
       //
       if ( !BP_WriteData(hPart, (LPBYTE)dwRegionStart, dwRegionLength) )
       {
           EdbgOutputDebugString("ERROR: StoreImageToBootMedia: Failed towrite region to BINFS partition (start=0x%x, length=0x%x).\r\n",dwRegionStart, dwRegionLength);
           return(FALSE);
       }
       // update our TOC?
       //
       if ((g_pTOC->id[g_dwTocEntry].dwLoadAddress ==g_BINRegionInfo.Region[nCount].dwRegionStart) &&
            g_pTOC->id[g_dwTocEntry].dwTtlSectors ==FILE_TO_SECTOR_SIZE(dwRegionLength) )
       {
           g_pTOC->id[g_dwTocEntry].dwStoreOffset = dwStoreOffset;
           g_pTOC->id[g_dwTocEntry].dwJumpAddress = 0; // Filled upon return toOEMLaunch
           g_pTOC->id[g_dwTocEntry].dwImageType = g_ImageType;
           g_pTOC->id[g_dwTocEntry].sgList[0].dwSector =FILE_TO_SECTOR_SIZE(g_dwLastWrittenLoc);
           g_pTOC->id[g_dwTocEntry].sgList[0].dwLength = g_pTOC->id[g_dwTocEntry].dwTtlSectors;
           // copy Kernel Region to SDRAM for jump
           memcpy((void*)g_pTOC->id[g_dwTocEntry].dwLoadAddress,(void*)dwRegionStart, dwRegionLength);
           OALMSG(TRUE, (TEXT("Updateded TOC!\r\n")));
       }
       else if( (dwChainStart == g_BINRegionInfo.Region[nCount].dwRegionStart)&&
                 (dwChainLength ==g_BINRegionInfo.Region[nCount].dwRegionLength))
       {
           //  Update our TOC for Chainregion
           g_pTOC->chainInfo.dwLoadAddress = dwChainStart;
           g_pTOC->chainInfo.dwFlashAddress =FILE_TO_SECTOR_SIZE(g_dwLastWrittenLoc);
           g_pTOC->chainInfo.dwLength =FILE_TO_SECTOR_SIZE(dwMaxRegionLength[nCount]);
           OALMSG(TRUE, (TEXT("Written Chain Region to the Flash\n")));
           OALMSG(TRUE, (TEXT("LoadAddress = 0x%X; FlashAddress = 0x%X; Length= 0x%X\n"),
                                 g_pTOC->chainInfo.dwLoadAddress,
                                 g_pTOC->chainInfo.dwFlashAddress,
                                 g_pTOC->chainInfo.dwLength));
           // Now copy it to the SDRAM
           memcpy((void *)g_pTOC->chainInfo.dwLoadAddress, (void*)dwRegionStart, dwRegionLength);
       }
    }
    //create extended partition in whatever is left
    //
   hPartEx = BP_OpenPartition( NEXT_FREE_LOC,
                               USE_REMAINING_SPACE,
                                PART_DOS32,
                                TRUE,
                               PART_OPEN_ALWAYS);
    if(hPartEx == INVALID_HANDLE_VALUE )
    {
       OALMSG(OAL_WARN, (TEXT("*** WARN: StoreImageToBootMedia: Failed toopen/create Extended partition ***\r\n")));
    }
   OALMSG(OAL_FUNC, (TEXT("-WriteOSImageToBootMedia\r\n")));
   return(TRUE);
}
可以看到,程序首先调用VALID_TOC()函数校验TOC数据,再调用BP_OpenPartition()函数进行分区。这个分区函数非常重要,其代码如下:
HANDLE BP_OpenPartition(DWORD dwStartSector,DWORD dwNumSectors, DWORD dwPartType, BOOL fActive, DWORD dwCreationFlags)
{
       DWORD dwPartIndex;
       BOOL fExists;
       ASSERT (g_pbMBRSector);
       if (!IsValidMBR()) // 检测MBR的存放位置,如果没有检测到,则低格flash,再创建分区
                 {
           DWORD dwFlags = 0;
           if (dwCreationFlags == PART_OPEN_EXISTING)
                           {
                RETAILMSG(1,(TEXT("OpenPartition: Invalid MBR. Cannot open existing partition 0x%x.\r\n"), dwPartType));
                return INVALID_HANDLE_VALUE;
           }
           RETAILMSG(1, (TEXT("OpenPartition: Invalid MBR.  Formatting flash.\r\n")));
           if (g_FlashInfo.flashType == NOR)
                           {
                dwFlags |=FORMAT_SKIP_BLOCK_CHECK;
           }
           //BP_LowLevelFormat (0, g_FlashInfo.dwNumBlocks, dwFlags);// 没有检测到MBR,则先低格flash.这里不能从block0开始!!!
           // lqm changed.10-04-13
           BP_LowLevelFormat (13, g_FlashInfo.dwNumBlocks-13, dwFlags);
           dwPartIndex = 0;
           fExists = FALSE;
       }
       else
                 {
           fExists = GetPartitionTableIndex(dwPartType, fActive,&dwPartIndex);        
       }
       RETAILMSG(1, (TEXT("OpenPartition: Partition Exists=0x%x for part0x%x.\r\n"), fExists, dwPartType));
       if (fExists)
                 {
           // Partition was found.  
           if (dwCreationFlags == PART_CREATE_NEW)
                return INVALID_HANDLE_VALUE;
           if (g_partStateTable[dwPartIndex].pPartEntry == NULL)
                           {
                // Open partition.  If this is the boot section partition, thenfile pointer starts after MBR
               g_partStateTable[dwPartIndex].pPartEntry = (PPARTENTRY)(g_pbMBRSector +PARTTABLE_OFFSET + sizeof(PARTENTRY)*dwPartIndex);
               g_partStateTable[dwPartIndex].dwDataPointer = 0;
           }            
           return (HANDLE)&g_partStateTable[dwPartIndex];            
       }
       else
                 {
           // If there are already 4 partitions, or creation flag specifiedOPEN_EXISTING, fail.
           if ((dwPartIndex == NUM_PARTS) || (dwCreationFlags ==PART_OPEN_EXISTING))
                return INVALID_HANDLE_VALUE;
           // Create new partition
           return CreatePartition (dwStartSector, dwNumSectors, dwPartType,fActive, dwPartIndex);// 没有检测到MBR,低格flash后,再创建分区
       }
       return INVALID_HANDLE_VALUE;
}
该函数有五个传入参数,其功能如下:
dwStartSector:分区的起始页
dwNumSectors:分区页数
dwPartType:分区格式
fActive:true -> 指示该分区为活动分区
dwCreationFlags:PART_OPEN_ALWAYS指示如果分区不存在就创建该分区,存在就OPEN该分区,返回分区句柄。
再看刚才WriteOSImageToBootMedia()函数中传进来的参数:
dwStartSector:(IMAGE_START_BLOCK+1)*PAGES_PER_BLOCK=(1+8+4+1)*32=448;
dwNumSectors: 由USB下载得到的映像大小计算出最终需要的页数;
dwPartType: 0x21,表示BinFS格式;
fActive: 1;
dwCreationFlags: PART_OPEN_ALWAYS。
有了入口参数,我们再详细分析函数所实现的功能。
程序第一句通过调用IsValidMBR()函数,判断MBR是否存在。该函数代码如下:
static BOOL IsValidMBR()
{
    //Check to see if the MBR is valid
    //MBR block is always located at logical sector 0
   g_dwMBRSectorNum = GetMBRSectorNum();        
   RETAILMSG (1, (TEXT("IsValidMBR: MBR sector = 0x%x\r\n"), g_dwMBRSectorNum));
    if((g_dwMBRSectorNum == INVALID_ADDR) || !FMD_ReadSector (g_dwMBRSectorNum,g_pbMBRSector, NULL, 1))
        {
       return FALSE;  
    }
        //lqm added for test.10-04-12
        RETAILMSG(1, (TEXT("g_pbMBRSector[0] = 0x%x\r\n"), g_pbMBRSector[0]));
        RETAILMSG(1, (TEXT("g_pbMBRSector[1] = 0x%x\r\n"), g_pbMBRSector[1]));
        RETAILMSG(1, (TEXT("g_pbMBRSector[2] = 0x%x\r\n"), g_pbMBRSector[2]));
        RETAILMSG(1, (TEXT("g_pbMBRSector[SECTOR_SIZE-2] = 0x%x\r\n"),g_pbMBRSector[SECTOR_SIZE-2]));
        RETAILMSG(1, (TEXT("g_pbMBRSector[SECTOR_SIZE-1] = 0x%x\r\n"),g_pbMBRSector[SECTOR_SIZE-1]));
        //end added.
   return ((g_pbMBRSector[0] == 0xE9) &&
        (g_pbMBRSector[1] == 0xfd) &&
        (g_pbMBRSector[2] == 0xff) &&
        (g_pbMBRSector[SECTOR_SIZE-2] == 0x55) &&
        (g_pbMBRSector[SECTOR_SIZE-1] == 0xAA));
}
该函数首先通过GetMBRSectorNum()函数获得MBR存放的段,其代码如下:
// 获得保存MBR的那个页
static DWORD GetMBRSectorNum ()
{
   //DWORD dwBlockNum = 0;       //K9F1G08
   DWORD dwBlockNum = 13;//K9F1208.lqm changed.must matched with uboot.
   while (dwBlockNum < g_FlashInfo.dwNumBlocks)
        {
       if (!IS_BLOCK_UNUSABLE (dwBlockNum)) //判断当前block是否可用
                 {
           return (dwBlockNum * g_FlashInfo.wSectorsPerBlock);
                 }
       dwBlockNum++;
    }
   return INVALID_ADDR;
}
这里dwBlockNum变量定义了MBR存放的block。结合UBoot分区表的设置,MBR被保存到了block13。因此,这里直接赋予dwBlockNum=13。这里必须严格和Uboot里面的分区表对应。该函数返回block13对应的起始段。
关于MBR:NANDFLASH由BLOCK和Sector组成,所以NANDFLASH的第0 BLOCK,第1 Sector为主引导扇区,FDISK程序写到该扇区的内容称为主引导记录(MBR)。该记录占用512个字节,它用于硬盘启动时将系统控制权交给用户指定的,并在分区表中登记了的某个操作系统区。
一个扇区的硬盘主引导记录MBR由如图6-15所示的4个部分组成。
·主引导程序(偏移地址0000H--0088H),它负责从活动分区中装载,并运行系统引导程序。
·出错信息数据区,偏移地址0089H--00E1H为出错信息,00E2H--01BDH全为0字节。
·分区表(DPT,Disk Partition Table)含4个分区项,偏移地址01BEH--01FDH,每个分区表项长16个字节,共64字节为分区项1、分区项2、分区项3、分区项4。
·结束标志字,偏移地址01FE--01FF的2个字节值为结束标志55AA,如果该标志错误系统就不能启动。

  
0000-0088
  
  
  
Master Boot Record
  
主引导程序
  
  
主引导
  
程序
  
  
0089-01BD
  
  
出错信息数据区
  
  
数据区
  
  
01BE-01CD
  
  
分区项1(16字节)
  
  
  
分区表
  
  
  
01CE-01DD
  
  
分区项2(16字节)
  
  
01DE-01ED
  
  
分区项3(16字节)
  
  
01EE-01FD
  
  
分区项4(16字节)
  
  
01FE
  
  
55
  
  
结束标志
  
  
01FF
  
  
AA
  
结合这些,我们再分析IsValidMBR()函数的代码,通过调用FMD_ReadSector()函数,读取所在page的数据,然后判断其帧头是否为0xE9,0xFd,0xFF,帧尾是否为0x55,0xAA。如果没有,则表示对应页没有保存MBR信息,这时程序调用BP_LowLevelFormat()函数,格式化对应block,并创建MBR。BP_LowLevelFormat()函数代码如下:
BOOL BP_LowLevelFormat(DWORD dwStartBlock,DWORD dwNumBlocks, DWORD dwFlags)
{
   dwNumBlocks = min (dwNumBlocks, g_FlashInfo.dwNumBlocks);// 要格式化的block不能超过flash总的block数

   RETAILMSG(1,(TEXT("Enter LowLevelFormat [0x%x, 0x%x].\r\n"),dwStartBlock, dwStartBlock + dwNumBlocks - 1));

    //Erase all the flash blocks.
    //检测每一个Sector,每个BLOCK只要有一个Sector不能读写这个块都会被处理成坏块,这样才能保证系统的稳定性。
    if(!EraseBlocks(dwStartBlock, dwNumBlocks, dwFlags))//这里有问题,dwNumBlocks应为dwStartBlock+ dwNumBlocks - 1!lqm.10-04-14
       return(FALSE);

    //Determine first good starting block
   while (IS_BLOCK_UNUSABLE (dwStartBlock) && dwStartBlock <g_FlashInfo.dwNumBlocks)
        {
       dwStartBlock++;
    }

    if(dwStartBlock >= g_FlashInfo.dwNumBlocks)
        {
       RETAILMSG(1,(TEXT("BP_LowLevelFormat: no goodblocks\r\n")));        
       return FALSE;
    }

    //MBR goes in the first sector of the starting block.  This will be logical sector 0.
   g_dwMBRSectorNum = dwStartBlock * g_FlashInfo.wSectorsPerBlock;

        //Create an MBR.
   CreateMBR();// 创建MBR,这时创建的MBR里面的分区表信息是空的。

   RETAILMSG (1, (TEXT("Done.\r\n\r\n")));
   return(TRUE);
}
值得注意的是,这里的CreateMBR()函数创建的是一个空MBR,只是放了帧头和帧尾,具体里面的分区信息并没有填充。这些信息是在后面填充的。CreateMBR()函数代码如下:
static BOOL CreateMBR()
{
    //This, plus a valid partition table, is all the CE partition manager needs torecognize
    //the MBR as valid. It does not contain boot code.

   memset (g_pbMBRSector, 0xff, g_FlashInfo.wDataBytesPerSector);
   g_pbMBRSector[0] = 0xE9;
   g_pbMBRSector[1] = 0xfd;
   g_pbMBRSector[2] = 0xff;
   g_pbMBRSector[SECTOR_SIZE-2] = 0x55;
   g_pbMBRSector[SECTOR_SIZE-1] = 0xAA;

    //Zero out partition table so that mspart treats entries as empty.
    //因为这里还没有分区,故先将分区表清零
   memset (g_pbMBRSector+PARTTABLE_OFFSET, 0, sizeof(PARTENTRY) *NUM_PARTS);

   return WriteMBR();//将上面的信息写到指定的block
}  
可以看出,这里面只是填入了一个帧头和帧尾的信息。
格式化完成后,再调用CreatePartition()函数进行分区,代码如下:
static HANDLE CreatePartition (DWORD dwStartSector,DWORD dwNumSectors, DWORD dwPartType, BOOL fActive, DWORD dwPartIndex)
{
   DWORD dwBootInd = 0;

   RETAILMSG(1, (TEXT("CreatePartition: Enter CreatePartition for0x%x.\r\n"), dwPartType));

    if(fActive)
       dwBootInd |= PART_IND_ACTIVE;
    if(dwPartType == PART_BOOTSECTION || dwPartType == PART_BINFS || dwPartType ==PART_XIP)//NK映像只读!
       dwBootInd |= PART_IND_READ_ONLY;   

    // If start sector is invalid, it means find next free sector
    if(dwStartSector == NEXT_FREE_LOC)
        {        

       dwStartSector = FindFreeSector();
       if (dwStartSector == INVALID_ADDR)
                 {
           RETAILMSG(1, (TEXT("CreatePartition: can't find freesector.\r\n")));
           return INVALID_HANDLE_VALUE;
       }

        // Start partitions on the next block ifthey are currently on the wrong block type.
       if (dwStartSector % g_FlashInfo.wSectorsPerBlock)
                 {            
           DWORD dwBlock = dwStartSector / g_FlashInfo.wSectorsPerBlock;
           if (IS_PART_READONLY(dwBootInd) != IS_BLOCK_READONLY(dwBlock))
                           {
                dwStartSector = (dwBlock+1) *g_FlashInfo.wSectorsPerBlock;
           }
       }
    }

    if(IS_PART_READONLY(dwBootInd)) // 分区是否只读
        {

       // Allow read-only partitions to go to the end of disk, if requested.
       if (dwNumSectors == USE_REMAINING_SPACE)
                 {

           DWORD dwLastLogSector = LastLogSector();
           if (dwLastLogSector == INVALID_ADDR)
                return INVALID_HANDLE_VALUE;

           dwNumSectors = dwLastLogSector- dwStartSector + 1;
       }
    }
   else
        {

       DWORD dwLastLogSector = LastLogSector();
       if (dwLastLogSector == INVALID_ADDR)
           return INVALID_HANDLE_VALUE;

       // Determine the number of blocks to reserve for the FAL compaction whencreating an extended partition.
       DWORD dwReservedBlocks = g_FlashInfo.dwNumBlocks /PERCENTAGE_OF_MEDIA_TO_RESERVE;
       if((dwReservedBlocks = g_FlashInfo.dwNumBlocks /PERCENTAGE_OF_MEDIA_TO_RESERVE) < MINIMUM_FLASH_BLOCKS_TO_RESERVE)
                 {
           dwReservedBlocks = MINIMUM_FLASH_BLOCKS_TO_RESERVE;
       }

       DWORD dwNumMaxSectors = dwLastLogSector - dwStartSector + 1 -dwReservedBlocks * g_FlashInfo.wSectorsPerBlock;

       // If dwNumSectors was provided, validate it isn't past the max.
       // If dwNumSectors is USE_REMAINING_SPACE, fill disk with max sectors.
       if ((dwNumSectors == USE_REMAINING_SPACE)  || (dwNumMaxSectors <  dwNumSectors)) {
           RETAILMSG(1, (TEXT("CreatePartition: Num sectors set to 0x%x toallow for compaction blocks.\r\n"), dwNumMaxSectors));
           dwNumSectors = dwNumMaxSectors ;   
       }
    }


    if(!AreSectorsFree (dwStartSector, dwNumSectors))
        {
       RETAILMSG (1, (TEXT("CreatePartition: sectors [0x%x, 0x%x]requested are out of range or taken by another partition\r\n"),dwStartSector, dwNumSectors));
       return INVALID_HANDLE_VALUE;
    }

   RETAILMSG(1, (TEXT("CreatePartition: Start = 0x%x, Num = 0x%x.\r\n"),dwStartSector, dwNumSectors));
    //准备分区信息写入分区表
   AddPartitionTableEntry (dwPartIndex, dwStartSector, dwNumSectors,(BYTE)dwPartType, (BYTE)dwBootInd);

    if(IS_PART_READONLY(dwBootInd))
        {
       if (!WriteLogicalNumbers (dwStartSector, dwNumSectors, TRUE))
                 {
           RETAILMSG(1, (TEXT("CreatePartition: can't mark sectorinfo.\r\n")));
           return INVALID_HANDLE_VALUE;
       }
    }

    if(!WriteMBR())// 这里写MBR的分区表信息???
       return INVALID_HANDLE_VALUE;

   g_partStateTable[dwPartIndex].pPartEntry = (PPARTENTRY)(g_pbMBRSector +PARTTABLE_OFFSET + sizeof(PARTENTRY)*dwPartIndex);
   g_partStateTable[dwPartIndex].dwDataPointer = 0;

   return (HANDLE)&g_partStateTable[dwPartIndex];            
}
该分区的传入参数作用如下:
dwStartSector:起始分区的逻辑页
dwNumSectors:分区占用的页数.如果为-1则视为将余下的所有空间划为一个分区。
dwPartType:分区类型
fActive: TRUE则创建活动分区
dwPartIndex:MBR的分区入口索引
调用该函数实现BinFS系统分区,写入MBR信息。
再回到WriteOSImageToBootMedia()函数,前面创建完BinFS系统分区之后,再调用BP_SetDataPointer()函数设置分区数据指针,调用BP_WriteData()函数将数据写到nandflash。可见,完成写系统映像到nand的函数,最终是调用了BP_WriteData()函数。
烧到nand之后,再更新TOC数据,之后再调用BP_OpenPartition()函数给剩下的flash分区,格式为PART_DOS32,即FAT32格式。
给剩下的flash分区之后,程序就会跳转到映像下载地址,加载系统并运行。到此,Eboot完成使命,将控制权交给WINCE系统。






欢迎光临 九鼎创展论坛 (http://bbs.9tripod.com/) Powered by Discuz! X3.2