解决DM368 UBL读取UBoot无法启动的问题

解决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()中):

  1. 启动后,从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;
    }
    
  2. 读取到了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 00
    	01 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将会被加载到这个地址
    
  3. 设置内存区域。所有调用UTIL_allocMem函数的内存分配都必须在这些语句执行之前完成。

    if ((magicNum == UBL_MAGIC_BIN_IMG) || (magicNum == UBL_MAGIC_DMA))  //内存的分配操作需要在这一条语句之前结束……
    {
        // Set the copy location to final run location
        rxBuf = (Uint8 *)gNandBoot.ldAddress;
        // Free temp memory rxBuf used to point to
        UTIL_setCurrMemPtr((void *)((Uint32)
                                    UTIL_getCurrMemPtr() - (APP_IMAGE_SIZE >> 1)));
    }
    
  4. 读取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 number
        if (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;
    
  5. 回到启动流程启动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个初步结论:

  1. 启动失败原因:读取第一块UBoot失败后,读取了第二块区域的垃圾数据。
  2. 垃圾数据的来源应该是烧写时烧写工具通过正常写入流程写入的,因此这些垃圾数据也有正常的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 number
    if (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 number
    if (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步走:

  1. 备份第一页的Magic Number。
  2. 按块擦除Nand上的数据。
  3. 回写MagicNumber到第一页
  4. 从有效区域将UBoot内容回写到有问题的部分

以恢复最后一块UBoot为例:

if (needrecovery == 1)
{
    //need recovery
    DEBUG_printString("Last U-Boot Error\r\n");
    Uint8 *MagicHeaderPage = pagebuf1;       //只能按块擦除,因此需要备份第一个page的MagicHeader
    Uint32 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. 总结

  1. malloc之类的代码一定要查看return value是否有效
  2. uboot下的nand命令对nand设备的读写擦除、mm/md/mw等内存读写命令等的使用要熟悉
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值