解决DM368 UBL读取UBoot无法启动的问题
目录
- 解决DM368 UBL读取UBoot无法启动的问题
- 0. 问题描述
- 1. 卡死原因分析
- 1.1 UBL启动流程
- 1.2. 启动异常点分析
- 2. 修改烧录工具SFT,正确写入多份UBoot
- 2.1 修改点
- 2.2 测试
- 3. 循环自恢复
- 3.1 自恢复的逻辑
- 3.2 自恢复功能的实现
- 3.2.1 初始化缓冲区
- 3.2.2 页对页复制的实现
- 3.2.3 定位最后一块Block的位置和检测的实现
- 3.2.4 检测非最后一块的UBoot的实现
- 3.2.5 恢复功能的实现逻辑
- 4. 总结
解决DM368 UBL读取UBoot无法启动的问题
0. 问题描述
最近遇到了一个DM368突然无法启动的问题。
问题现象:开机后无法加载Uboot,卡死在UBL处,现象大概为在0x19处找到MagicNum后未正常进入系统,跳到0x1a找到第二个MagicNumber后卡死
1. 卡死原因分析
初步分析启动流程,卡死原因是UBL启动时加载了错误的UBoot。
1.1 UBL启动流程
UBL启动的流程如下(主要函数在Common\ubl\src\nandboot.c里的NANDBOOT_copy()中):
-
启动后,从UBoot分区读取每块的第一页,并检测第一页的前4个字节是否为Magic Number:0xA1ACED**
NAND_startAgain: if (blockNum > DEVICE_NAND_UBL_SEARCH_END_BLOCK) {return E_FAIL; // NAND boot failed and return fail to main } for (count = blockNum;count <= DEVICE_NAND_UBL_SEARCH_END_BLOCK;count++) {if (NAND_readPage(hNandInfo, count, 0,rxBuf) != E_PASS){DEBUG_printString("NAND_readPage Fail at #");DEBUG_printHexInt(count);DEBUG_printString(".\r\n");continue;}magicNum = ((Uint32 *)rxBuf)[0];/* Valid magic number found */if ((magicNum & 0xFFFFFF00) == MAGIC_NUMBER_VALID){blockNum = count;DEBUG_printString("Valid magicnum, ");DEBUG_printHexInt(magicNum);DEBUG_printString(", found in block ");DEBUG_printHexInt(blockNum);DEBUG_printString(".\r\n");break;} } // Never found valid header in any page 0 of any of searched blocks if (count > DEVICE_NAND_UBL_SEARCH_END_BLOCK) {DEBUG_printString("No valid boot image found!\r\n");return E_FAIL; } -
读取到了Magic Number之后,会将后续的几个启动系统的数据进行保存,用于加载U-Boot:
gNandBoot.entryPoint = *(((Uint32 *)(&rxBuf[4])));/* 1.UBoot程序入口点 */ gNandBoot.numPage = *(((Uint32 *)(&rxBuf[8]))); /* 2.UBoot总共占用的页数 */ gNandBoot.block = *(((Uint32 *)(&rxBuf[12]))); /* 3.这份UBoot存在系统的哪一个Block */ gNandBoot.page = *(((Uint32 *)(&rxBuf[16]))); /* 4.这份UBoot在本块的起始页 */ gNandBoot.ldAddress = *(((Uint32 *)(&rxBuf[20]))); /* 5.Uboot在内存中加载到的地方 */UBoot中使用nand dump命令,可以查到nand中这些数据的存储如下(以存放在第一分区的UBoot为例):
U-boot > nand dump 320000 //0x320000 = 0x19(Block) * 64 (Pages/Block) * 2048(Bytes/Page) Page 00320000 dump:66 ed ac a1 00 00 08 81 9e 00 00 00 19 00 00 0001 00 00 00 00 00 08 81 ff ff ff ff ff ff ff ff这个CPU是Little-Endian的,所以需要字节倒序: Magic number = 66 ed ac a1 = 0xa1aced66,指示系统类型 entryPoint = 0x81080000,U-Boot会从这里启动 numPage = 0x0000009e,指示UBoot占用0x9e页 block = 0x00000019,说明这份UBoot从第0x19个Block开始读取 page = 0x00000001,即UBoot在这个Block的第一页 ldAddress = 0x81080000,UBoot将会被加载到这个地址 -
设置内存区域。所有调用UTIL_allocMem函数的内存分配都必须在这些语句执行之前完成。
if ((magicNum == UBL_MAGIC_BIN_IMG) || (magicNum == UBL_MAGIC_DMA)) //内存的分配操作需要在这一条语句之前结束…… {// Set the copy location to final run locationrxBuf = (Uint8 *)gNandBoot.ldAddress;// Free temp memory rxBuf used to point toUTIL_setCurrMemPtr((void *)((Uint32)UTIL_getCurrMemPtr() - (APP_IMAGE_SIZE >> 1))); } -
读取UBoot的数据复制到启动地址,如果读取出错则回到第一步查找下一个分区(Magic Number)
NAND_retry: /* initialize block and page number to be used for read */ block = gNandBoot.block; page = gNandBoot.page; // Perform the actual copying of the application from NAND to RAM for (i = 0; i < gNandBoot.numPage; i++) {// if page goes beyond max number of pages increment block number and reset page numberif (page >= hNandInfo->pagesPerBlock){page = 0;block++;}readError = NAND_readPage(hNandInfo, block,page++, (&rxBuf[i * (hNandInfo->dataBytesPerPage)])); /* Copy the data */// We attempt to read the app data twice. If we fail twice then we go look for a new// application header in the NAND flash at the next block.if (readError != E_PASS){if (failedOnceAlready){blockNum++;DEBUG_printString("Start Again From Block:");DEBUG_printHexInt(blockNum);DEBUG_printString("\r\n");goto NAND_startAgain; //重试失败,查找下一个Magic Number}else{failedOnceAlready = TRUE; //每次读取失败,重试读取一次DEBUG_printString("failed At Block:");DEBUG_printHexInt(blockNum);DEBUG_printString(".Retry.\r\n");goto NAND_retry;}} }// Application was read correctly, so set entrypoint gEntryPoint = gNandBoot.entryPoint; return E_PASS; -
回到启动流程启动UBoot。
1.2. 启动异常点分析
从0中的问题描述可以看出,在读取0x19 Block里的UBoot时,检测到发生了ECC错误,导致无法读取本块UBoot,跳到下一块UBoot(0x1C)进行读取。
进入UBoot,读取Nand中位于0x1C的数据(地址为0x1C(Block) * 64(Page/Block) * 2048(Byte/Page) = 0x380000),发现第一页是正常的Magic Number,第二页本应该是UBoot的内容,却被写入了垃圾数据。
初步分析,问题应该是读取第一块UBoot失败后,读取了第二块区域的垃圾数据导致启动失败。
而由于检测数据有效性的手段仅有ECC,因此程序本身无法判断第二块区域是否为正常的UBoot数据,导致启动失败。
从上述分析中我们能够得出以下2个初步结论:
- 启动失败原因:读取第一块UBoot失败后,读取了第二块区域的垃圾数据。
- 垃圾数据的来源应该是烧写时烧写工具通过正常写入流程写入的,因此这些垃圾数据也有正常的ECC。
结合上述分析,可以初步判断368的烧写工具存在Bug,虽然做了多UBoot的备份逻辑,但是实际上只写入了1份UBoot。
2. 修改烧录工具SFT,正确写入多份UBoot
2.1 修改点
烧录工具分为SFH/SFT两部分。前者是电脑上运行的程序,后者是通过串口传输到手机上手机运行的程序。
烧录软件时,SFH会先将SFT通过串口烧录到板子上,板子接收到串口传输的SFT后启动烧录系统,再接收UBL和UBoot进行烧写。
结合上述分析流程可知,问题出在SFT写入的流程中。
分析sft代码(路径:flash-utils\Common\sft\src\uartboot.c),发现问题出在函数LOCAL_NANDWriteHeaderAndData()上。
该函数的流程是,根据UBoot的大小,将UBoot拷贝N份并存储在Flash中。然而,由于烧写工具存在Bug,重复写入时Source指针未复位,导致写入失败。
只需要加入下面两行代码,记录下原始数据srcBuf的首地址,在循环写入时恢复指针指向即可


2.2 测试
测试很简单,使用UBoot的nand命令破坏第一块UBoot数据之后,再重启即可。
进入UBoot,使用命令:
nand erase 0x320000 1
清除掉首地址为0x320000的Block
-
nand erase命令使用方法:
nand erase [addr] [len]addr是擦除的首地址,由于只能按块擦除,因此首地址只能是每个block的首地址。即该地址需要按照0x20000(1Block*64(Pages/Block)*2048(Bytes/Page) = 0x20000Byte)对齐,否则会报错
len是擦除长度,一样按照0x20000向上对齐。例如len为1,也按照0x20000擦除整个Block
部分清除掉第一块UBoot后,系统正常从第二块UBoot启动,问题初步解决。
3. 循环自恢复
上面的方法只是创建了多个备份,能够在第一块UBoot数据出错后使用第二块UBoot。但是错误的UBoot数据不会自行恢复,因此应该在启动时加入对UBoot数据的恢复。
3.1 自恢复的逻辑
自恢复的逻辑如下:

3.2 自恢复功能的实现
3.2.1 初始化缓冲区
由于在程序中检测到MagicNumber之后,分配内存函数会无法正常使用:
if ((magicNum == UBL_MAGIC_BIN_IMG) || (magicNum == UBL_MAGIC_DMA)) //内存的分配操作需要在这一条语句之前结束……
{rxBuf = (Uint8 *)gNandBoot.ldAddress;UTIL_setCurrMemPtr((void *)((Uint32)UTIL_getCurrMemPtr() - (APP_IMAGE_SIZE >> 1)));
}
因此需要在这之前给我们的操作分配2Page的缓冲区。
Uint8 *pagebuf1 = NULL,*pagebuf2 = NULL;
Uint32 InitMyBuffer(NAND_InfoHandle hNandInfo)
{pagebuf1 = (Uint8*)UTIL_allocMem(hNandInfo->dataBytesPerPage);pagebuf2 = (Uint8*)UTIL_allocMem(hNandInfo->dataBytesPerPage);if(pagebuf1 == NULL || pagebuf2 == NULL)return E_FAIL;return E_PASS;
}
3.2.2 页对页复制的实现
在恢复过程中,需要进行页对页的复制。逻辑很简单,就是从source读取并写入目的位置。
实现相关函数如下:
Uint32 NANDCopyPage2Page(NAND_InfoHandle hNandInfo, Uint32 srcblock, Uint32 srcpage,Uint32 dstblock, Uint32 dstpage,Uint32 copyBlockCnt, Uint32 copyPageCnt)
//传入参数:Nand句柄,源Block&page,目的Block&page,要复制的Block和页数
{if (srcblock == dstblock && srcpage == dstpage) //参数错误{return 0x0BADADD0;}if (copyBlockCnt == 0 && copyPageCnt == 0){return 0x0BADADD1;}Uint32 totalPage = copyBlockCnt *(hNandInfo->pagesPerBlock) + copyPageCnt; //根据传入的Block和Page计算总共要复制的页数Uint8 *pageBuf;pageBuf = pagebuf2;int i;for (i = 0; i < totalPage; i++){if (NAND_readPage(hNandInfo, srcblock, srcpage,pageBuf) != E_PASS){DEBUG_printString("Error Read Page At Blk ");DEBUG_printHexInt(srcblock);DEBUG_printString(" Page ");DEBUG_printHexInt(srcpage);DEBUG_printString("\r\n");return E_FAIL;}if (NAND_writePage(hNandInfo, dstblock, dstpage,pageBuf) != E_PASS){DEBUG_printString("Error Write Page At Blk ");DEBUG_printHexInt(dstblock);DEBUG_printString(" Page ");DEBUG_printHexInt(dstpage);DEBUG_printString("Mark it as BadBlock.");DEBUG_printString("\r\n");NAND_badBlockMark(hNandInfo, dstblock);return E_FAIL;}srcpage++;if (srcpage >= hNandInfo->pagesPerBlock){srcpage = 0;srcblock++;}dstpage++;if (dstpage >= hNandInfo->pagesPerBlock){dstpage = 0;dstblock++;}}return E_PASS;
}
3.2.3 定位最后一块Block的位置和检测的实现
定位的方法即从当前Block开始往后找,找到最后一个MagicNumber所在的Block:
DEBUG_printString("Locate Last Block U-boot:\r\n");
Uint32 checkcnt, lastcnt, mymgc;
Uint32 rdpage, rdblk;
Uint8 needrecovery = 0;for (checkcnt = blockNum;checkcnt <= DEVICE_NAND_UBL_SEARCH_END_BLOCK;checkcnt++)
{if (NAND_readPage(hNandInfo, checkcnt, 0, pagebuf1) != E_PASS){DEBUG_printString("NAND_readPage Fail at #");DEBUG_printHexInt(checkcnt);DEBUG_printString(".\r\n");continue;}mymgc = ((Uint32 *)pagebuf1)[0];if ((mymgc & 0xFFFFFF00) == MAGIC_NUMBER_VALID){lastcnt = checkcnt;}
}
DEBUG_printString("Last U-Boot is at Blk #");
DEBUG_printHexInt(lastcnt);
DEBUG_printString(".\r\n");
检测数据有效性的方式只能通过ECC进行检测,因此只需要读取所有数据并检测是否有ECC错误就好:
rdpage = 0; rdblk = lastcnt;
for (i = 0; i < gNandBoot.numPage; i++)
{// if page goes beyond max number of pages increment block number and reset page numberif (rdpage >= hNandInfo->pagesPerBlock){rdpage = 0;rdblk++;}readError = NAND_readPage(hNandInfo, rdblk, rdpage++, pagebuf1); /* 读取数据 */if (readError != E_PASS){needrecovery = 1; //读取数据出错,说明ECC校验不通过break;}
}
3.2.4 检测非最后一块的UBoot的实现
在原有读取逻辑中增加变量lastFailedMagicNumBlock,记下最后一次失败的MagicNumber的块数:
NAND_retry:
/* initialize block and page number to be used for read */
block = gNandBoot.block;
page = gNandBoot.page;
// Perform the actual copying of the application from NAND to RAM
for (i = 0; i < gNandBoot.numPage; i++)
{// if page goes beyond max number of pages increment block number and reset page numberif (page >= hNandInfo->pagesPerBlock){page = 0;block++;}readError = NAND_readPage(hNandInfo, block,page++, (&rxBuf[i * (hNandInfo->dataBytesPerPage)])); /* Copy the data */if (readError != E_PASS){if (failedOnceAlready){lastFailedMagicNumBlock = blockNum; //---记录下最后一次有MagicNumber的Block---blockNum++;DEBUG_printString("Start Again From Block:");DEBUG_printHexInt(blockNum);DEBUG_printString("\r\n");goto NAND_startAgain;}else{failedOnceAlready = TRUE;DEBUG_printString("failed At Block:");DEBUG_printHexInt(blockNum);DEBUG_printString(".Retry.\r\n");goto NAND_retry;}}
}
之后判断该块数和正常启动的块数之间的差异是否为3Block大小(这里一个UBoot占3Block)
if(lastFailedMagicNumBlock !=0 && lastFailedMagicNumBlock - blockNum >=3 && isMyBufValid == TRUE)
{//恢复数据...
}
3.2.5 恢复功能的实现逻辑
恢复数据需要分4步走:
- 备份第一页的Magic Number。
- 按块擦除Nand上的数据。
- 回写MagicNumber到第一页
- 从有效区域将UBoot内容回写到有问题的部分
以恢复最后一块UBoot为例:
if (needrecovery == 1)
{//need recoveryDEBUG_printString("Last U-Boot Error\r\n");Uint8 *MagicHeaderPage = pagebuf1; //只能按块擦除,因此需要备份第一个page的MagicHeaderUint32 TotalBlock = 3;NAND_readPage(hNandInfo, lastcnt, 0, MagicHeaderPage);if (NAND_eraseBlocks(hNandInfo, lastcnt, TotalBlock) != E_PASS) //擦除有问题的3个Block{DEBUG_printString("Erase Error.\r\n");}DEBUG_printHexInt(TotalBlock);DEBUG_printString(" Erase OK.Now Recovery Magic Num.\r\n");if (NAND_writePage(hNandInfo, lastcnt, 0, MagicHeaderPage) != E_PASS) //恢复MagicNumber{DEBUG_printString("Write Magic Error.\r\n");}DEBUG_printString("Write Magic OK.\r\n");NANDCopyPage2Page(hNandInfo, blockNum, 1, lastcnt, 1, 0, gNandBoot.numPage); //按页复制Uboot,恢复上个分区的数据DEBUG_printString("Recovery Success.\r\n");
}
4. 总结
- malloc之类的代码一定要查看return value是否有效
- uboot下的nand命令对nand设备的读写擦除、mm/md/mw等内存读写命令等的使用要熟悉
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
