九鼎创展论坛
标题: 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
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 |