最近在解一个SD卡不能烧写WinCE的镜像的问题,问题是出现在UT上的,UT提供了一种功能,可以利用保存了镜像文件的SDMMC卡来升级image.一般来讲,在UT下的一些utils都是比较轻量级的,比如就说这个升级image的功能,仅仅使用了一个很简单的FAT文件系统和一个精简的SDMMC的驱动,而在image中,光SDMMC驱动就分成了SDMMC总线驱动,SDMMC 客户端驱动和 SDMMC控制器驱动,FAT文件系统也是由微软提供的FATFS.dll来实现的。
遇到的故障现象是有些SD卡上的镜像文件不能被烧写程序搜寻到,报不能找到WINCEIMG.CKS错误。寻找问题的开始是从比较问题卡和正常卡烧写image过程中的异同,为了让两张测试卡处于相同的环境,我用WinHEX工具以磁盘镜像的方式将两张盘做了镜像拷贝,按照我的意思是想让它们完全一下,但事实证明我忽略了很重要的一点导致走了很大的弯路。用过WinHEX的朋友都知道,它的磁盘管理工具具有很强的数据分析功能,能分别以逻辑扇区和物理扇区两种方式来分析磁盘数据,我在做镜像操作的过程中,错误的使用了逻辑扇区镜像,这导致这两张卡的物理扇区布局是完全不同的。接下来我开始用debug的信息来调试这两张卡,从源码上分析烧写程序首先是调用FAT文件系统的初始化函数,它做的第一个工作就是找到分区的引导扇区DBR,DBR和MBR不同,MBR每个磁盘最多只有一个,也可以没有,而DBR是每个分区一个,磁盘有多少个分区就有多少个DBR扇区。找到DBR扇区就可以得到相应分区的物理参数信息(BPB),这些信息包括总扇区数,每扇区的字节数,FAT表数目和大小,根目录的位置等等,这些信息对于FAT文件系统来讲是基本参数,直接决定后面的文件操作的正常与否。所有的数据读取操作都是以扇区为单位进行的,对应的工作程序就是SDMMC的驱动代码了。
所以我第一个工作就开始来比较DBR扇区了,结果出乎我意料,不一样!!!正常的卡从第一个物理扇区读出了正确的512字节的DBR,而错误的卡从第二个物理扇区读出了错误的DBR。我首先开始怀疑是SDMMC的驱动代码的问题,然后开始全力排查驱动,直到我追到最底层的往SDMMC的控制寄存器发读取扇区的command了,我还是没有发现驱动存在任何问题。垂头丧气去吃了晚饭准备开始加班了。
第二次打开WinHEX,把问题卡插到读卡器中,这次不知道怎么学乖了,从磁盘管理器的“物理媒介”栏把SD卡打开,忽然发现里面的数据很陌生了,和正常的卡做比较,发现物理扇区并没有一一对上,问题卡物理扇区上的数据和在烧写程序中读到的值相同。这说明UT的SDMMC的驱动并没有问题,问题出在FAT文件系统对DBR的解析上。
重新打开UT下的FAT源代码,考察其对DBR的辨识算法,其相关代码如下:
for (i = 0; i < 1024; i++) {
s = diskFuncP->read(ctxP->diskP, i, buf, 1);
if ((((buf[0] == 0xEB) && (buf[2] == 0x90)) || (buf[0] == 0xE9))
&& ((buf[510] == 0x55) && (buf[511] == 0xaa))
&& ((strncmp("FAT", &buf[54], 3)== 0)||(strncmp("FAT32", &buf[82], 5)== 0))) {
//判定扇区i是DBR扇区,开始计算FAT分区的参数
}
代码很明朗,就是从0开始搜索物理扇区,一个一个扇区的读,直到发现有一个扇区对应比特符合DBR的特征为止,然后它会把这个扇区的编号作为一个base_sector_number开始进行FAT表和根目录的定位。我马上意识到,FAT不可能这么简单,估计是有伪DBR扇区在作怪,用WinHEX查,在问题卡上果然还存在一个扇区和DBR格式相像,进一步证明它才是真正的DBR扇区,问题的出现就是因为它的物理扇区编号位于伪DBR扇区的编号之后,我们的程序找错了DBR扇区。
问题已经找到了,接下来该考虑怎么解决了。怎样分别真假DBR呢,这个让我头疼了好久,因为解决这个问题需要非常detail的了解FAT,而且还要考虑兼容FAT16/32两种格式。你或许会说,查MBR不就搞定了么,DBR是可以通过查询MBR来得知其所占据的扇区号的。但问题是很多SD卡往往都没有MBR,不信你拿读卡器在PC上Format一张卡,让WinHEX去读物理0扇区,肯定没有MBR。(很多U盘工具可以将SD卡格式化成多个分区,这个时候会有MBR生成,Windows会把有MBR的U盘或SD卡作为硬盘看待,图标会显示到硬盘栏而不是可移动存储设备栏)
看完了FAT白皮书,还是没有发现有什么DBR有什么防伪特征,到微软的WinCE的sourcecode中找FATFS的source code看,也没有总结出好的经验。
没办法,用最笨的也是最根本的方法,验证FAT表和根目录的有效性。因为错误的DBR扇区往往仅仅剩下一个躯壳,与之相对应的FAT表和根目录往往被后来的格式化所破坏。
开始学习DBR每个byte代表的意思,写代码求出FAT表的偏移和根目录的便宜,然后检查FAT表的前2-4个字节,如果是FAT16,那么应该前两个字节一定是是0xF8 0xFF,如果是FAT32,前四个自己一定是0xF8 0xFF 0xFF 0x0F;如果FAT表有备份,那么还要检查第二个FAT表的内容是否合法;然后找到根目录的第11个字节,这个字节是根目录属性,用这个值和0x08与,等于零的话说明是错误的根目录路径。经过这些判定,伪DBR扇区基本上会识别出来,用这种方法,那些不能用的SDMMC卡终于可以工作了。理论上讲,还会有不能正确辨识的情况存在,但是能排除绝大部分卡就起到效果了。希望知道更完美解决方案的哥们提供好的建议,谢谢。下面是源代码,蓝色字部分是本文的解决方案加入的代码。
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
for (i = 0; i < 1024; i++) {
s = ctxP->diskFuncP->read(ctxP->diskP, i, buf, 1);
if ((((buf[0] == 0xEB) && (buf[2] == 0x90)) || (buf[0] == 0xE9))
&& ((buf[510] == 0x55) && (buf[511] == 0xaa))
&& ((strncmp("FAT", &buf[54], 3)== 0)||(strncmp("FAT32", &buf[82], 5)== 0)))
{
int nFatsector,nRootsector,nFATnum;
p = (FAT_BiosParameterBlock_T *) buf;
RootDirSectors= ((p->maxRoot *32)+(p->bytesPerSector-1))/p->bytesPerSector ; nFatsector=ctxP->reservedBlocks + i + p->reservedSectors;
if (p->FATSz16 != 0)
FatSz = p->FATSz16 ;
else
FatSz = p->FATSz32 ;
nFATnum=p->numberOfFAT;
nRootsector=nFatsector+nFATnum*FatSz;
DM_SerialPrintf("looks like DBR sector = %d nFATnum=%d",i,nFATnum);
if(nFATnum<1)
continue;
s = ctxP->diskFuncP->read(ctxP->diskP, nFatsector, buf, 1);
DM_SerialPrintf("DBR sector = %d",i);
DM_SerialPrintf("FAT1=%d buf[0]==0x%x buf[1]=0x%x buf[2]=0x%x buf[3]=0x%x ,nFatsector=0x%x",nFatsector,buf[0],buf[1],buf[2],buf[3],nFatsector);
if(p->FATSz16!=0)
{
if( buf[0]!=0xF8 || buf[1]!=0xFF ) //is not valide FAT16
continue;
}
else
{
if( buf[0]!=0xF8 || buf[1]!=0xFF || buf[2]!=0xFF || buf[3]!=0x0F ) //is not valide FAT32
continue;
}
if(nFATnum>1) //verify the second FAT table
{
s = ctxP->diskFuncP->read(ctxP->diskP, nFatsector+FatSz, buf, 1);
DM_SerialPrintf("FAT2=%d buf[0]==0x%x buf[1]=0x%x buf[2]=0x%x buf[3]=0x%x ,nFatsector=0x%x",nFatsector+FatSz,buf[0],buf[1],buf[2],buf[3],nFatsector);
if(p->FATSz16!=0)
{
if( buf[0]!=0xF8 || buf[1]!=0xFF ) //is not valide FAT16
continue;
}
else
{
if( buf[0]!=0xF8 || buf[1]!=0xFF || buf[2]!=0xFF || buf[3]!=0x0F ) //is not valide FAT32
continue;
}
}
s = ctxP->diskFuncP->read(ctxP->diskP, nRootsector, buf, 1);
DM_SerialPrintf("ROOTDIR=%d buf[0]==0x%x buf[11]=0x%x numberOfFAT=%d",nRootsector,buf[0],buf[11],nFATnum);
if((buf[0]!=0xE5)&&(buf[11]&0x08)==0) //not a valid root directory
continue;
ctxP->baseBlock = ctxP->reservedBlocks + i;
found = 1;
}
}