备注:处理器为S3C2451,flash为K9K8G08U0D
1. ECC分配标准
先来看三星NAND FLASH spare区域的分配标准:
图1
2. StepldrECC的处理
Stepldr的写采用自己的函数FMD_LB_WriteSector_Steploader,stepldr采用8bit的ECC纠错,为什么呢?S3C2451 FLASH控制器部分给出说明:
1. Auto boot by: The boot code istransferred into 8-KB Steppingstone after reset. After the boot code is
transfered, boot code will beexecuted on the Steppingstone.
Note: IROMboot support 8Bit ECC correction on Nand device booting
此函数通过设置NFCONF[24:23]=1来支持8bit ECC。还有需要注意的是,这里采用另一组参数:
#define DEFAULT_TACLS (1) // 1 HCLK (7.5ns)
#define DEFAULT_TWRPH0 (4) // 5 HCLK(37.5ns)
#define DEFAULT_TWRPH1 (1) // 2 HCLK(15ns)
2.1 写stepldr镜像文件时ECC的操作
当写入512字节(不包括spare数据)的数据后, 8bit ECC模块内部产生ECC奇偶校验码,这些校验码自动更新到NF8MECC0、NF8MECC1、NF8MECC2、NF8MECC3寄存器中,然后可以把校验码写入到spare区域,下面来看代码:
typedef struct
{
UINT32 n8MECC0; // 8MECC0
UINT32 n8MECC1; // 8MECC1
UINT32 n8MECC2; // 8MECC2
UINT8 n8MECC3; // 8MECC3
} MECC8;
此结构体的定位,为什么n8MECC3需要定义为8位的,见寄存器的说明:
图2
MECC8 t8MECC[4];
// initializevariable.
for(i = 0; i < 4; i++) {
t8MECC[i].n8MECC0 = 0x0;
t8MECC[i].n8MECC1 = 0x0;
t8MECC[i].n8MECC2 = 0x0;
t8MECC[i].n8MECC3 = 0x0;
}
每往main区域写入512字节的数据,都产生ECC校验码并保存在t8MECC[nSectorLoop]中。
// Write each Sector in the Page. (4 Sectorper Page, Loop 4 times.)
for (nSectorLoop = 0; nSectorLoop <SECTORS_PER_PAGE; nSectorLoop++)
{
// Initialize ECC register
NF_MECC_UnLock();
NF_RSTECC();
// Special case to handle un-alignedbuffer pointer.
if( ((DWORD)(pSectorBuff+nSectorLoop*SECTOR_SIZE)) & 0x3)
{
// Write the data
for(i=0; i<SECTOR_SIZE/sizeof(DWORD);i++)
{
wrdata =(pSectorBuff+nSectorLoop*SECTOR_SIZE)[i*4+0];
wrdata |=(pSectorBuff+nSectorLoop*SECTOR_SIZE)[i*4+1]<<8;
wrdata |=(pSectorBuff+nSectorLoop*SECTOR_SIZE)[i*4+2]<<16;
wrdata |=(pSectorBuff+nSectorLoop*SECTOR_SIZE)[i*4+3]<<24;
NF_WRDATA_WORD(wrdata);
}
}
else
{
WrPage512(pSectorBuff+nSectorLoop*SECTOR_SIZE);
}
NF_MECC_Lock();
while(!(s2450NAND->NFSTAT&(1<<7))) ;
s2450NAND->NFSTAT|=(1<<7);
// Read out the ECC value generated by HW
t8MECC[nSectorLoop].n8MECC0 =NF_8MECC0();
t8MECC[nSectorLoop].n8MECC1 =NF_8MECC1();
t8MECC[nSectorLoop].n8MECC2 =NF_8MECC2();
t8MECC[nSectorLoop].n8MECC3 =(NF_8MECC3() & 0xff);
}
这里要重点注意了,先来看数据手册的说明:
图3
也就是说,如果使用512字节的NAND FLASH memory,可以直接把校验码写入到spare区域,但如果使用多于512字节的NAND FLASH memory(上面的代码是写入4*512字节的数据),就需要先拷贝校验码到其他的存储空间(比如DRAM,这里对应局部变量数据t8MECC),等写完所有的main数据后,再把拷贝后的校验码写到spare区域。
//写完所有的main数据,现在写入到spare区域
for(nSectorLoop = 0; nSectorLoop < 4;nSectorLoop++)
{
NF_WRDATA_WORD(t8MECC[nSectorLoop].n8MECC0); // 4 byte n8MECC0
NF_WRDATA_WORD(t8MECC[nSectorLoop].n8MECC1); // 4 byte n8MECC1
NF_WRDATA_WORD(t8MECC[nSectorLoop].n8MECC2); // 4 byte n8MECC2
NF_WRDATA_BYTE((t8MECC[nSectorLoop].n8MECC3) & 0xff); // 1 byten8MECC3
}
由此可知stepldr镜像文件对应的每页的16 字节spare区域安排如下:
图4
2.2 读stepldr镜像文件时ECC的操作
Sepldr内容是由S3C2451的IROM代码来读取的,读取时校验码的比较也是由IROM来完成的,为什么eboot在写入stepldr时采用8bit ECC,是因为:
Note: IROMboot support 8Bit ECC correction on Nand device booting
3. EbootECC的处理
Eboot中写eboot和NK镜像文件采用1bit ECC
3.1 写Eboot和NK镜像文件时ECC的操作
由FMD_LB_WriteSector函数来实现,首先是写入main区域,然后重生ECC码,再写入spare区域。
// Write eachSector in the Page. (4 Sector per Page, Loop 4 times.)
for (nSectorLoop = 0; nSectorLoop< SECTORS_PER_PAGE; nSectorLoop++)
{
// Initialize ECC register
NF_RSTECC();
NF_MECC_UnLock();
// Special case tohandle un-aligned buffer pointer.
if( ((DWORD)(pSectorBuff+nSectorLoop*SECTOR_SIZE)) & 0x3)
{
// Write the data
for(i=0;i<SECTOR_SIZE/sizeof(DWORD); i++)
{
wrdata= (pSectorBuff+nSectorLoop*SECTOR_SIZE)[i*4+0];
wrdata|= (pSectorBuff+nSectorLoop*SECTOR_SIZE)[i*4+1]<<8;
wrdata|= (pSectorBuff+nSectorLoop*SECTOR_SIZE)[i*4+2]<<16;
wrdata|= (pSectorBuff+nSectorLoop*SECTOR_SIZE)[i*4+3]<<24;
NF_WRDATA_WORD(wrdata);
}
}
else
{
WrPage512(pSectorBuff+nSectorLoop*SECTOR_SIZE);
}
NF_MECC_Lock();
// Read out the ECC value generated by HW
MECCBuf[nSectorLoop]= NF_RDMECC0();
}
当读或是写main数据(不包括spare数据)完成时,自动把ECC模块产生的校验码更新到NFMECC0、NFMECC1寄存器中,并且ECC状态寄存器的值不会改变。这时可以从这些寄存器中读取其值。
// Write theSectorInfo data to the media
// NOTE: This hardware is odd:only a byte can be written at a time and it must reside in the
// upper byte of a USHORT.
if(pSectorInfoBuff) // SectorInfoBuff啊 瞒 乐促搁, 角力 Sector Info甫持绰促.
{
#if CHECK_SPAREECC
NF_RSTECC();
NF_SECC_UnLock();
#endif
// Write the first reserved field (DWORD)
NF_WRDATA_BYTE(pSectorInfoBuff->bBadBlock);
NF_WRDATA_WORD(pSectorInfoBuff->dwReserved1);
NF_WRDATA_BYTE(pSectorInfoBuff->bOEMReserved);
#if CHECK_SPAREECC
NF_SECC_Lock();
#endif
NF_WRDATA_BYTE(pSectorInfoBuff->wReserved2&0xff);
NF_WRDATA_BYTE((pSectorInfoBuff->wReserved2>>8)&0xff);
}
else // SectorInfoBuff啊 厚绢乐促搁, 歹固 data甫 持绰促. (0xffffffff ffffffff)
{
// Make sure weadvance the Flash's write pointer (even though we aren't writing the SectorInfodata)
for(i=0;i<sizeof(SectorInfo)/sizeof(DWORD); i++)
{
NF_WRDATA_WORD(0xffffffff);
}
}
上面的内容是把SectorInfo结构体的数据写入到spare中,并且为这些数据产生ECC校验码,下面接着把main数据的ECC校验码接着写入spare中
// Write the ECC value to the flash
NF_WRDATA_WORD(MECCBuf[0]);
NF_WRDATA_WORD(MECCBuf[1]);
NF_WRDATA_WORD(MECCBuf[2]);
NF_WRDATA_WORD(MECCBuf[3]);
上面在写入SectorInfo结构体bBadBlock、dwReserved1和bOEMReserved数据到spare的时候,为这三个数据产生ECC校验码,下面从NFSECC寄存器中读取出ECC校验码并写入到spare中
#if CHECK_SPAREECC
if(pSectorInfoBuff)
{
SECCBuf =NF_RDSECC();
NF_WRDATA_WORD(SECCBuf);
}
#endif
根据上面的代码,可以每页(2048字节=4*512)对应的spare区域安排如下:
图5
其中bBadBlock表示坏块信息,如果其值为0xFF表示是好块,如果不是则为坏块;dwReserved1用来保存逻辑页(2048字节)的数量,比如,如果是第1block的第1页,那么它的值=64页,因为我们的flash一个block=64页。bOEMReserved,我们可以用来设置block的属性,比如设置此block为预留block,或是只读block等。wReserved2用来为FAL只是数据是否有效(Indicates data is valid for the FAL( flash abstraction layer))。
3.2 读Eboot和NK镜像文件时ECC的操作
由FMD_LB_ReadSector函数来实现,先读取spare区域的数据
(1) 读取spare区域数据,并进行ECC校验
//读取图5中的前8字节的数据
if (pSectorInfoBuff)
{
#if CHECK_SPAREECC
NF_RSTECC();
NF_SECC_UnLock();
#endif
pSectorInfoBuff->bBadBlock= NF_RDDATA_BYTE();
pSectorInfoBuff->dwReserved1= NF_RDDATA_WORD();
pSectorInfoBuff->bOEMReserved= NF_RDDATA_BYTE();
#if CHECK_SPAREECC
NF_SECC_Lock();
#endif
pSectorInfoBuff->wReserved2= NF_RDDATA_BYTE();
pSectorInfoBuff->wReserved2|= (NF_RDDATA_BYTE()<<8);
}
// If there is noSectorInfoBuffer, Just read and waste.
else
{
for(i=0;i<sizeof(SectorInfo)/sizeof(DWORD); i++)
rddata = (DWORD)NF_RDDATA_WORD(); // readand trash the data
}
//接着读取保存MECC0校验数据(这是写入main数据时重生的ECC校验码)
for (nSectorLoop = 0; nSectorLoop< SECTORS_PER_PAGE; nSectorLoop++)
{
MECCBuf[nSectorLoop]= NF_RDDATA_WORD();
}
//读取出spare的ECC,然后写入到NFSECCD中,并校验
#if CHECK_SPAREECC
if (pSectorInfoBuff)
{
SECCBuf =NF_RDDATA_WORD();
NF_WRSECCD((SECCBuf&0xff)|((SECCBuf<<8)&0xff0000));
nRetEcc =NF_ECC_ERR0;
if(!ECC_CorrectData(startSectorAddr, (LPBYTE)pSectorInfoBuff, nRetEcc,ECC_CORRECT_SPARE))
returnFALSE;
}
#endif
上面代码依据于数据手册下面的部分:
图6
当把数据写入到NFSECCD后,通过NFECCERR0来定为ECC出错的具体信息,如下图:
图7
下面重点来看ECC_CorrectData函数
BOOLECC_CorrectData(SECTOR_ADDR sectoraddr, LPBYTE pData, UINT32 nRetEcc,ECC_CORRECT_TYPE nType)
{
DWORD nErrStatus;
DWORD nErrDataNo;
DWORD nErrBitNo;
UINT32 nErrDataMask;
UINT32 nErrBitMask = 0x7;
BOOL bRet = TRUE;
if (nType == ECC_CORRECT_MAIN)
{
//RETAILMSG(1,(TEXT("ECC_CorrectData()--->ECC_CORRECT_MAIN\r\n")));
nErrStatus = 0;
nErrDataNo = 7;
nErrBitNo = 4;
nErrDataMask =0x7ff;
}
else if (nType ==ECC_CORRECT_SPARE)
{
//RETAILMSG(1,(TEXT("ECC_CorrectData()--->ECC_CORRECT_SPARE\r\n")));
nErrStatus = 2;
nErrDataNo = 21;
nErrBitNo = 18;
nErrDataMask = 0xf;
}
else
{
//RETAILMSG(1,(TEXT("ECC_CorrectData()--->value=%d\r\n"),nType));
return FALSE;
}
switch((nRetEcc>>nErrStatus)& 0x3)
{
case 0: // No Error
//RETAILMSG(1,(TEXT("ECC_CorrectData()--->noerror\r\n")));
bRet= TRUE;
break;
case 1: // 1-bit Error(Correctable)
RETAILMSG(1,(TEXT("%cECCcorrectable error(0x%x). Byte:%d, bit:%d\r\n"),((nType==ECC_CORRECT_MAIN)?'M':'S'), sectoraddr,(nRetEcc>>nErrDataNo)&nErrDataMask,(nRetEcc>>nErrBitNo)&nErrBitMask));
(pData)[(nRetEcc>>nErrDataNo)&nErrDataMask]^= (1<<((nRetEcc>>nErrBitNo)&nErrBitMask));
bRet= TRUE;
break;
case 2: // Multiple Error
RETAILMSG(1,(TEXT("%cECCUncorrectable error(0x%x)\r\n"), ((nType==ECC_CORRECT_MAIN)?'M':'S'),sectoraddr));
bRet= FALSE;
break;
case 3: // ECC area Error
RETAILMSG(1,(TEXT("%cECCarea error\r\n"), ((nType==ECC_CORRECT_MAIN)?'M':'S')));
default:
bRet= FALSE;
break;
}
return bRet;
}
此函数需要密切结合NFECCERR0寄存器来理解,见下图:
图8
(2) 读取main区域数据,并进行ECC校验
这部分代码原理和上面的基本一样
// Read eachSector in the Page. (4 Sector per Page, Loop 4 times.)
for (nSectorLoop = 0; nSectorLoop< SECTORS_PER_PAGE; nSectorLoop++)
{
// calculate startaddress of each Sector
NewDataAddr =nSectorLoop * SECTOR_SIZE;
// Set address forrandom access
NF_CMD(CMD_RDO);
NF_ADDR((NewDataAddr)&0xff);
NF_ADDR((NewDataAddr>>8)&0xff);
NF_CMD(CMD_RDO2);
// Initialize ECCmodule
NF_RSTECC();
NF_MECC_UnLock();
// Special case to handle un-aligned bufferpointer.
if( ((DWORD)(pSectorBuff+nSectorLoop*SECTOR_SIZE)) & 0x3)
{
for(i=0;i<SECTOR_SIZE/sizeof(DWORD); i++)
{
rddata= (DWORD) NF_RDDATA_WORD();
(pSectorBuff+nSectorLoop*SECTOR_SIZE)[i*4+0]= (BYTE)(rddata & 0xff);
(pSectorBuff+nSectorLoop*SECTOR_SIZE)[i*4+1]= (BYTE)(rddata>>8 & 0xff);
(pSectorBuff+nSectorLoop*SECTOR_SIZE)[i*4+2]= (BYTE)(rddata>>16 & 0xff);
(pSectorBuff+nSectorLoop*SECTOR_SIZE)[i*4+3]= (BYTE)(rddata>>24 & 0xff);
}
}
// usual case tohandle 4byte aligned buffer pointer
else
{
RdPage512(pSectorBuff+nSectorLoop*SECTOR_SIZE); // Read page/sector data.
}
NF_MECC_Lock();
// Check ECC aboutMain Data with MECC parity codel
NF_WRMECCD0(((MECCBuf[nSectorLoop]&0xff00)<<8)|(MECCBuf[nSectorLoop]&0xff) );
NF_WRMECCD1(((MECCBuf[nSectorLoop]&0xff000000)>>8)|((MECCBuf[nSectorLoop]&0xff0000)>>16));
nRetEcc =NF_ECC_ERR0;
if(!ECC_CorrectData(startSectorAddr, pSectorBuff+nSectorLoop*SECTOR_SIZE,nRetEcc, ECC_CORRECT_MAIN))
{
RETAILMSG(1,(TEXT("#### FMD_DRIVER:::FMD_LB_READSECTOR 7\r\n")));
returnFALSE;
}
}