S3C2450自动升级
2010年06月14日
S3C2450自动升级
在BSP包中,有两个bootloader文件夹,一个命名为bootloader,另一个命名为bootloader_update。Bootloader文件夹用于USB下载,调试用,bootloader_update用于生产,自动升级用。下面重点介绍bootloader_update文件夹。
Bootloader_update文件夹下有四个文件夹,分别是BLCOMMON,Eboot_boot,FAT_LIB以及IROM_SD_TOOL。BLCOMMON文件夹编译后生成oal_blcommon.lib文件,供Eboot_boot调用。FAT_LIB文件夹里面包含三个静态库文件FAT_LIB.lib,HSMMCEBOOT_LIB.lib以及SDREAD_UPDATE.lib。这些静态库封装了一些用于升级的函数。Eboot_boot文件夹编译后将会产生生产用的Eboot.bin文件,该文件即能够启动系统,也能够通过硬件平台的按键控制,实现自动升级。IROM_SD_TOOL文件夹编译后,会将前面的Eboot.bin文件封装成SD卡识别的程序,最终生成IROM_SD_EBOOT.nb0。通过三星提供的IROM_Fusing_Tool工具,将该文件烧到SD卡后,2450平台就能够实现从SD卡启动了。当然前提是硬件本身要设置跳线成SD卡启动。
主程序中(Eboot_boot文件夹)定义了三个重要的变量:AUTO_UPDATE_FROM_SD;
ERASE_ALL_FLASH_BLOCKS;
SHOW_UPDATE_LOGO。
当AUTO_UPDATE_FROM_SD为真时,Eboot将会进入升级程序,为假时将引导flash中的NK,启动系统。
当ERASE_ALL_FLASH_BLOCKS为真时,Eboot将会格式化flash,然后依次升级block0.nb0,eboot.bin,nk.bin,随后启动系统。
当SHOW_UPDATE_LOGO为真时,LCD上将会出现升级文件的提示,否则显示开机LOGO。
其中AUTO_UPDATE_FROM_SD标志位通过硬件平台的按键判断,当拨动电源键后在两秒之内回拨到LOCK位置,Eboot将识别到该LOCK标志,这时将AUTO_UPDATE_FROM_SD置高,程序进入升级状态。
ERASE_ALL_FLASH_BLOCKS标志位通过从SD卡中的update.txt文件中读取。
AUTO_UPDATE_FROM_SD的相关代码如下: if(1 == UPDATA_ALL_IMAGE) { AUTO_UPDATE_FROM_SD =TRUE; ERASE_ALL_FLASH_BLOCKS =TRUE; SHOW_UPDATE_LOGO = TRUE; } else if( _FAT_Boot_ReadFile(_BOOT_TYPE_SD_BIN, _UPDATE_FILE_DETECT) ||auto_updatekey_isPressed() ) { //自动升级健按下进行自动升级 AUTO_UPDATE_FROM_SD=TRUE; } else { AUTO_UPDATE_FROM_SD = FALSE; } 其中_FAT_Boot_ReadFile()函数用于从SD卡中读取_UPDATE_FILE_DETECT文件,该字符串在最前面有如下定义:
#define _UPDATE_FILE_DETECT "DETECT BIN"
auto_updatekey_isPressed()函数用于检测拍照键是否按下,从这里可以看出,如果SD卡中存在detect.bin文件,则升级时不用再按住拍照键了。如果没有则必须按住拍照键。
Flash格式化的程序如下: if(1)// format flash. { //ADDED BY ZHOUPENG FOR TEST ReadDeviceSerialNumber(Serial_Block_Number,pBuf8); ReadDeviceSerialNumber(Smit_Device_Serial_Block_Nu mber,pBuf9); OALMSG(TRUE, (TEXT(" ++Format FIL (Erase All Blocks)\r\n"))); if (VFL_Close() != VFL_SUCCESS) { OALMSG(TRUE, (TEXT("[ERR] VFL_Close() Failure\r\n"))); } if (WMR_Format_FIL() == FALSE32) { OALMSG(TRUE, (TEXT("[ERR] WMR_Format_FIL() Failure\r\n"))); } if (WMR_Format_VFL() == FALSE32) { OALMSG(TRUE, (TEXT("[ERR] WMR_Format_VFL() Failure\r\n"))); } if (WMR_Format_FTL() == FALSE32) { OALMSG(TRUE, (TEXT("[ERR] WMR_Format_FTL() Failure\r\n"))); } OALMSG(TRUE, (TEXT("[INF] You can not use VFL before Format VFL\r\n"))); OALMSG(TRUE, (TEXT(" --Format FIL (Erase All Blocks)\r\n"))); WriteDeviceSerialNumber(Serial_Block_Number,pBuf8) ; WriteDeviceSerialNumber(Smit_Device_Serial_Block_N umber,pBuf9); } 下载映像的函数如下: static BOOL DownloadImage (LPDWORD pdwImageStart, LPDWORD pdwImageLength, LPDWORD pdwLaunchAddr) { BYTE hdr[BL_HDRSIG_SIZE]; DWORD dwRecLen, dwRecChk, dwRecAddr; BOOL fIsFlash = FALSE; LPBYTE lpDest = NULL; int nPkgNum = 0; BYTE nNumDownloadFiles = 1; DWORD dwImageStart = 0; DWORD dwImageLength = 0; RegionInfo *pCurDownloadFile; int i; *pdwImageStart = *pdwImageLength = *pdwLaunchAddr = 0; g_DownloadManifest.dwNumRegions = 0 ; for( i = 0 ; i read image signature.\r\n"); HALT (BLERR_MAGIC); return (FALSE); } // An N000FF packet is manufactured by Platform Builder when we're // downloading multiple files or when we're downloading a .nb0 file. if (!memcmp (hdr, "N000FF\x0A", BL_HDRSIG_SIZE))//比较数据相等时memcmp返回0 { // read the packet checksum. // if (!OEMReadData (sizeof (DWORD), (LPBYTE) &dwRecChk)) { EdbgOutputDebugString("\r\nUnable to read download manifest checksum.\r\n"); HALT (BLERR_MAGIC); return (FALSE); } // read BIN region descriptions (start address and length). // if (!OEMReadData (sizeof (DWORD), (LPBYTE) &g_DownloadManifest.dwNumRegions) || !OEMReadData ((g_DownloadManifest.dwNumRegions * sizeof(RegionInfo)), (LPBYTE) &g_DownloadManifest.Region[0])) { EdbgOutputDebugString("\r\nUnable to read download manifest information.\r\n"); HALT (BLERR_MAGIC); return (FALSE); } // verify the packet checksum. if (!VerifyChecksum((g_DownloadManifest.dwNumRegions * sizeof(RegionInfo)), (LPBYTE) &g_DownloadManifest.Region[0], dwRecChk)) { EdbgOutputDebugString ("\r\nDownload manifest packet failed checksum verification.\r\n"); HALT (BLERR_CHECKSUM); return (FALSE); } if (g_pOEMMultiBINNotify) { g_pOEMMultiBINNotify((PDownloadManifest)&g_Downloa dManifest); } // look for next download... nNumDownloadFiles = (BYTE)(g_DownloadManifest.dwNumRegions + 1); // +1 to account for this packet. continue; } // Is this an old X000FF multi-bin packet header? It's no longer supported. else if (!memcmp (hdr, "X000FF\x0A", BL_HDRSIG_SIZE)) { EdbgOutputDebugString ("ERROR: The X000FF packet is an old-style multi-bin download manifest and it's no longer supported. \r\nPlease update your Platform Builder installation in you want to download multiple files.\r\n"); HALT (BLERR_MAGIC); return (FALSE); } // Is this a standard bin image? Check for the usual bin file signature. else if (!memcmp (hdr, "B000FF\x0A", BL_HDRSIG_SIZE))//lqm:下载xip.bin,nk.bin,eboot.bin时在这里执行 { g_bBINDownload = TRUE; if (!OEMReadData (sizeof (DWORD), (LPBYTE) &dwImageStart)//读取*.bin前7个头字节后的一个DWORD到dwImageStart。*.bin记录了其映像起始地址 || !OEMReadData (sizeof (DWORD), (LPBYTE) &dwImageLength))//读取上面接着的一个DWORD到dwImageLength。*.bin记录了其映像长度 { EdbgOutputDebugString ("Unable to read image start/length\r\n"); HALT (BLERR_MAGIC); return (FALSE); } } // If the header signature isn't recognized, we'll assume the // download file is a raw .nb0 file. // else { g_bBINDownload = FALSE; } if (!g_DownloadManifest.dwNumRegions) { g_DownloadManifest.dwNumRegions = 1; //声明*.bin有一条记录 g_DownloadManifest.Region[0].dwRegionStart = dwImageStart; //声明这条记录的起始地址 g_DownloadManifest.Region[0].dwRegionLength = dwImageLength;//声明这条记录的映像长度 // Provide the download manifest to the OEM. if (g_pOEMMultiBINNotify) { g_pOEMMultiBINNotify((PDownloadManifest)&g_Downloa dManifest); } } // Locate the current download manifest entry (current download file). pCurDownloadFile = &g_DownloadManifest.Region[g_DownloadManifest.dwNu mRegions - nNumDownloadFiles];//当前下载文件指向*.bin开始 // give the OEM a chance to verify memory.内存校验 if (g_pOEMVerifyMemory && !g_pOEMVerifyMemory (pCurDownloadFile->dwRegionStart, pCurDownloadFile->dwRegionLength)) { EdbgOutputDebugString ("!OEMVERIFYMEMORY: Invalid image\r\n");//lqm:很多情况下都会执行到这里!映像不能获得? HALT (BLERR_OEMVERIFY); return (FALSE); } // check for flash image. Start erasing if it is. if ( (fIsFlash = OEMIsFlashAddr(pCurDownloadFile->dwRegionStart))//l qm:校验起始地址是否在Flash相应地址区域,如果是则擦除相应下载区域 && !OEMStartEraseFlash (pCurDownloadFile->dwRegionStart, pCurDownloadFile->dwRegionLength)) { EdbgOutputDebugString ("Invalid Flash Address/Length\r\n"); HALT (BLERR_FLASHADDR); return (FALSE); } // if we're downloading a binary file, we've already downloaded part of the image when searching // for a file header. copy what we've read so far to the destination buffer, then finish downloading. if (!g_bBINDownload) { lpDest = OEMMapMemAddr (pCurDownloadFile->dwRegionStart, pCurDownloadFile->dwRegionStart); memcpy(lpDest, hdr, BL_HDRSIG_SIZE); // complete the file download... // read data block if (!OEMReadData ((pCurDownloadFile->dwRegionLength - BL_HDRSIG_SIZE), (lpDest + BL_HDRSIG_SIZE))) { EdbgOutputDebugString ("ERROR: failed when reading raw binary file.\r\n"); HALT (BLERR_CORRUPTED_DATA); return (FALSE); } } // we're downloading a .bin file - download each .bin record in turn... else//开始下载*.bin文件到DRAM中。 { while (OEMReadData (sizeof (DWORD), (LPBYTE) &dwRecAddr) && //读取*.bin的第n条记录的地址. OEMReadData (sizeof (DWORD), (LPBYTE) &dwRecLen) && //读取*.bin的第n条记录的长度. OEMReadData (sizeof (DWORD), (LPBYTE) &dwRecChk)) //读取*.bin的第n条记录的校验和. { // last record of .bin file uses sentinel values for address and checksum. // 最后一条记录表示结束.其地址和校验和均为0. if (!dwRecAddr && !dwRecChk) { break;//读到最后一条记录时跳出循环 } // map the record address (FLASH data is cached, for example) // 将存入的记录地址映射到指针地址lpDest lpDest = OEMMapMemAddr (pCurDownloadFile->dwRegionStart, dwRecAddr); // read data block if (!OEMReadData (dwRecLen, lpDest))//读取*.bin的第n条记录的内容到lpDest.这里先把*.bin的数据读到DRAM中。 { EdbgOutputDebugString ("****** Data record %d corrupted, ABORT!!! ******\r\n", nPkgNum); HALT (BLERR_CORRUPTED_DATA); return (FALSE); } if (!VerifyChecksum (dwRecLen, lpDest, dwRecChk))//每读取一条记录则校验一次数据 { EdbgOutputDebugString ("****** Checksum failure on record %d, ABORT!!! ******\r\n", nPkgNum); HALT (BLERR_CHECKSUM); return (FALSE); } // Look for ROMHDR to compute ROM offset. NOTE: romimage guarantees that the record containing // the TOC signature and pointer will always come before the record that contains the ROMHDR contents. // if (dwRecLen == sizeof(ROMHDR) && (*(LPDWORD) OEMMapMemAddr(pCurDownloadFile->dwRegionStart, pCurDownloadFile->dwRegionStart + ROM_SIGNATURE_OFFSET) == ROM_SIGNATURE)) { DWORD dwTempOffset = (dwRecAddr - *(LPDWORD)OEMMapMemAddr(pCurDownloadFile->dwRegionS tart, pCurDownloadFile->dwRegionStart + ROM_SIGNATURE_OFFSET + sizeof(ULONG))); ROMHDR *pROMHdr = (ROMHDR *)lpDest; // Check to make sure this record really contains the ROMHDR. // if ((pROMHdr->physfirst == (pCurDownloadFile->dwRegionStart - dwTempOffset)) && (pROMHdr->physlast == (pCurDownloadFile->dwRegionStart - dwTempOffset + pCurDownloadFile->dwRegionLength)) && (DWORD)(HIWORD(pROMHdr->dllfirst dlllast) && (DWORD)(LOWORD(pROMHdr->dllfirst dlllast)) { g_dwROMOffset = dwTempOffset; EdbgOutputDebugString("rom_offset=0x%x.\r\n", g_dwROMOffset); } } // verify partial checksum OEMShowProgress (nPkgNum ++); // lqm added Progress in LCD.10-01-22 while( nPkgNum>=8 ) { nPkgNum = 0; LcdDrawString(">"); } // end added. } } if (g_bBINDownload) { // Does this .bin file contain a TOC? if (*(LPDWORD) OEMMapMemAddr(pCurDownloadFile->dwRegionStart, pCurDownloadFile->dwRegionStart + ROM_SIGNATURE_OFFSET) == ROM_SIGNATURE) //+ ksk 20051219 { // Contain the kernel? if (IsKernelRegion(pCurDownloadFile->dwRegionStart, pCurDownloadFile->dwRegionLength)) { *pdwImageStart = pCurDownloadFile->dwRegionStart; *pdwImageLength = pCurDownloadFile->dwRegionLength; *pdwLaunchAddr = dwRecLen; } } // No TOC - not made by romimage. However, if we're downloading more than one // .bin file, it's probably chain.bin which doesn't have a TOC (and which isn't // going to be downloaded on its own) and we should ignore it. // else if (g_DownloadManifest.dwNumRegions == 1) { *pdwImageStart = pCurDownloadFile->dwRegionStart; *pdwImageLength = pCurDownloadFile->dwRegionLength; *pdwLaunchAddr = dwRecLen; } } else // Raw binary file. { *pdwImageStart = pCurDownloadFile->dwRegionStart; *pdwLaunchAddr = pCurDownloadFile->dwRegionStart; *pdwImageLength = pCurDownloadFile->dwRegionLength; } // write to flash if it's flash image if (fIsFlash) { // finish the flash erase if (!OEMFinishEraseFlash ()) { HALT (BLERR_FLASH_ERASE); return (FALSE); } // Before writing the image to flash, optionally check the image signature. if (g_bBINDownload && g_pOEMCheckSignature) { if (!g_pOEMCheckSignature(pCurDownloadFile->dwRegionSt art, g_dwROMOffset, *pdwLaunchAddr, TRUE)) HALT(BLERR_CAT_SIGNATURE); } } } while (--nNumDownloadFiles); if (fIsFlash) { nNumDownloadFiles = (BYTE)g_DownloadManifest.dwNumRegions; while (nNumDownloadFiles--) { if (!OEMWriteFlash (g_DownloadManifest.Region[nNumDownloadFiles].dwRe gionStart, g_DownloadManifest.Region[nNumDownloadFiles].dwReg ionLength)) { HALT (BLERR_FLASH_WRITE); return (FALSE); } } } return (TRUE); } 上面程序中有如下代码:
OEMShowProgress (nPkgNum ++);
while( nPkgNum>=8 )
{
nPkgNum = 0;
LcdDrawString(">");
}
该程序放在反复从SD卡中读取xip.bin的一条条记录的while循环中,这里是为了在升级界面上显示升级的进度。LcdDrawString(">")函数是调用了LcdDraw.c中的函数,用于在LCD上绘图。
这里一定要了解xip.bin的文件格式,否则很难理解整个数据流的流程。
Xip.bin以及NK.bin的组成:
组成:标记(7)+Image开始地址(1)+Image长度(1)
记录0地址+记录0长+记录0校验和+记录0内容(文件内容)
记录1地址+记录1长+记录1校验和+记录1内容(文件内容)
......
结束符号
从上面的程序可以看出,
if (!OEMReadData (BL_HDRSIG_SIZE, hdr))
{
EdbgOutputDebugString ("\r\nUnable to read image signature.\r\n");
HALT (BLERR_MAGIC);
return (FALSE);
}
即是将xip.bin的前7个字节的数据读到数组hdr,供后面分析所读的文件的类型。
if (!OEMReadData (sizeof (DWORD), (LPBYTE) &dwImageStart)//读取*.bin前7个头字节后的一个DWORD到dwImageStart。*.bin记录了其映像起始地址
|| !OEMReadData (sizeof (DWORD), (LPBYTE) &dwImageLength))//读取上面接着的一个DWORD到dwImageLength。*.bin记录了其映像长度
{
EdbgOutputDebugString ("Unable to read image start/length\r\n");
HALT (BLERR_MAGIC);
return (FALSE);
}
则是将紧接着前7个字节的两个WORD数据读到dwImageStart和dwImageLength。随后的
while (OEMReadData (sizeof (DWORD), (LPBYTE) &dwRecAddr) &&
//读取*.bin的第n条记录的地址.
OEMReadData (sizeof (DWORD), (LPBYTE) &dwRecLen) &&
//读取*.bin的第n条记录的长度.
OEMReadData (sizeof (DWORD), (LPBYTE) &dwRecChk))
//读取*.bin的第n条记录的校验和.
则开始一条条的解析xip.bin的记录。
if (!OEMReadData (dwRecLen, lpDest))
{
EdbgOutputDebugString ("****** Data record %d corrupted, ABORT!!! ******\r\n", nPkgNum);
HALT (BLERR_CORRUPTED_DATA);
return (FALSE);
}
用于将读取到的第N条记录存放到lpDest.
分析要下载的文件并写入flash的代码如下: void OEMLaunch( DWORD dwImageStart, DWORD dwImageLength, DWORD dwLaunchAddr, const ROMHDR *pRomHdr ) { DWORD dwPhysLaunchAddr; OALMSG(1, (TEXT("+OEMLaunch.\r\n"))); // If the user requested that a disk image (stored in RAM now) be written to the SmartMedia card, so it now. if (g_bDownloadImage) // && (g_pBootCfg->ConfigFlags & TARGET_TYPE_NAND)) { // Since this platform only supports RAM images, the image cache address is the same as the image RAM address. switch (g_ImageType) { case IMAGE_TYPE_STEPLDR://stepldr.bin if (!WriteRawImageToBootMedia(dwImageStart, dwImageLength, dwLaunchAddr)) { OALMSG(OAL_ERROR, (TEXT("ERROR: OEMLaunch: Failed to store image to Smart Media.\r\n"))); goto CleanUp; } OALMSG(TRUE, (TEXT("INFO: Step loader image stored to Smart Media. Please Reboot. Halting...\r\n"))); if (g_bAutoDownload == TRUE) { return ; } while(1); break; case IMAGE_TYPE_LOADER://eboot.bin 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 0 // don't write TOC after download Eboot if ( !TOC_Write() ) { EdbgOutputDebugString("*** OEMLaunch ERROR: TOC_Write failed! Next boot may not load from disk *** \r\n"); } TOC_Print(); #endif // by hmseo - 061123 } OALMSG(TRUE, (TEXT("INFO: Eboot image stored to Smart Media. Please Reboot. Halting...\r\n"))); if (g_bAutoDownload == TRUE) { return ; } while(1); break; case IMAGE_TYPE_RAMIMAGE://NK.bin OALMSG(1, (TEXT("+IMAGE_TYPE_RAMIMAGE dwImageStart:%08x.,dwLaunchAddr : %08x\r\n"),dwImageStart,dwLaunchAddr)); g_pTOC->id[g_dwTocEntry].dwLoadAddress = dwImageStart; g_pTOC->id[g_dwTocEntry].dwTtlSectors = FILE_TO_SECTOR_SIZE(dwImageLength); // 将NK.bin写到flash. if (!WriteOSImageToBootMedia(dwImageStart, dwImageLength, dwLaunchAddr)) { OALMSG(OAL_ERROR, (TEXT("ERROR: OEMLaunch: Failed to store image to Smart Media.\r\n"))); goto CleanUp; } EdbgOutputDebugString("INFO1:dwLaunchAdd : %08x g_pTOC->id[0].dwJumpAddress :%08x, g_pTOC->id[1].dwJumpAddress :%08x\r \r\n", dwLaunchAddr, g_pTOC->id[0].dwJumpAddress,g_pTOC->id[1].dwJumpAddr ess); if (dwLaunchAddr && (g_pTOC->id[g_dwTocEntry].dwJumpAddress != dwLaunchAddr)) { g_pTOC->id[g_dwTocEntry].dwJumpAddress = dwLaunchAddr; if ( !TOC_Write() ) //写TOC文件 { EdbgOutputDebugString("*** OEMLaunch ERROR: TOC_Write failed! Next boot may not load from disk *** \r\n"); } //masked by denis_wei for test 2009-02-05 TOC_Print(); } else { dwLaunchAddr= g_pTOC->id[g_dwTocEntry].dwJumpAddress; EdbgOutputDebugString("INFO2: using TOC[%d] dwJumpAddress: 0x%x\r\n", g_dwTocEntry, dwLaunchAddr); } break; } if(ERASE_ALL_FLASH_BLOCKS)//lqm added.10-01-22 { LcdSetStringPosition(1,15);// lqm added.10-01-22 LcdDrawString("Starting WINCE,Please Wait......");// lqm added.10-01-22 //LcdSetStringPosition(1,16);// lqm added.10-01-22 } if ( !ReadOSImageFromBootMedia( ) )//从FLASH读取映像到DRAM. { OALMSG(OAL_ERROR, (TEXT("OEMPlatformInit ERROR: Failed to load kernel region into RAM.\r\n"))); SpinForever(); } } OALMSG(1, (TEXT("waitforconnect\r\n"))); 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 the MMU off)... // dwPhysLaunchAddr = (DWORD)OALVAtoPA((void *)dwLaunchAddr); OALMSG(TRUE, (TEXT("INFO: OEMLaunch: Jumping to Physical Address 0x%Xh (Virtual Address 0x%Xh)...\r\n\r\n\r\n"), dwPhysLaunchAddr, dwLaunchAddr)); // Jump... if( g_bAutoDownload == TRUE ) { OALMSG(TRUE, (TEXT("INFO: auto update (BLOCK0IMAGE EBOOT NK) SUCCESS!\r\n")));} Launch(dwPhysLaunchAddr);//跳到指定物理地址运行程序. CleanUp: OALMSG(TRUE, (TEXT("ERROR: OEMLaunch: Halting...\r\n"))); SpinForever(); } 在上面代码中,如下代码用于将已经读入DRAM的数据烧写到flash. if (!WriteOSImageToBootMedia(dwImageStart, dwImageLength, dwLaunchAddr)) { OALMSG(OAL_ERROR, (TEXT("ERROR: OEMLaunch: Failed to store image to Smart Media.\r\n"))); goto CleanUp; } 整个bootloader代码量大,但是细下心来慢慢分析,其实不难理解。这里就不一一分析了。
2010年06月14日
S3C2450自动升级
在BSP包中,有两个bootloader文件夹,一个命名为bootloader,另一个命名为bootloader_update。Bootloader文件夹用于USB下载,调试用,bootloader_update用于生产,自动升级用。下面重点介绍bootloader_update文件夹。
Bootloader_update文件夹下有四个文件夹,分别是BLCOMMON,Eboot_boot,FAT_LIB以及IROM_SD_TOOL。BLCOMMON文件夹编译后生成oal_blcommon.lib文件,供Eboot_boot调用。FAT_LIB文件夹里面包含三个静态库文件FAT_LIB.lib,HSMMCEBOOT_LIB.lib以及SDREAD_UPDATE.lib。这些静态库封装了一些用于升级的函数。Eboot_boot文件夹编译后将会产生生产用的Eboot.bin文件,该文件即能够启动系统,也能够通过硬件平台的按键控制,实现自动升级。IROM_SD_TOOL文件夹编译后,会将前面的Eboot.bin文件封装成SD卡识别的程序,最终生成IROM_SD_EBOOT.nb0。通过三星提供的IROM_Fusing_Tool工具,将该文件烧到SD卡后,2450平台就能够实现从SD卡启动了。当然前提是硬件本身要设置跳线成SD卡启动。
主程序中(Eboot_boot文件夹)定义了三个重要的变量:AUTO_UPDATE_FROM_SD;
ERASE_ALL_FLASH_BLOCKS;
SHOW_UPDATE_LOGO。
当AUTO_UPDATE_FROM_SD为真时,Eboot将会进入升级程序,为假时将引导flash中的NK,启动系统。
当ERASE_ALL_FLASH_BLOCKS为真时,Eboot将会格式化flash,然后依次升级block0.nb0,eboot.bin,nk.bin,随后启动系统。
当SHOW_UPDATE_LOGO为真时,LCD上将会出现升级文件的提示,否则显示开机LOGO。
其中AUTO_UPDATE_FROM_SD标志位通过硬件平台的按键判断,当拨动电源键后在两秒之内回拨到LOCK位置,Eboot将识别到该LOCK标志,这时将AUTO_UPDATE_FROM_SD置高,程序进入升级状态。
ERASE_ALL_FLASH_BLOCKS标志位通过从SD卡中的update.txt文件中读取。
AUTO_UPDATE_FROM_SD的相关代码如下: if(1 == UPDATA_ALL_IMAGE) { AUTO_UPDATE_FROM_SD =TRUE; ERASE_ALL_FLASH_BLOCKS =TRUE; SHOW_UPDATE_LOGO = TRUE; } else if( _FAT_Boot_ReadFile(_BOOT_TYPE_SD_BIN, _UPDATE_FILE_DETECT) ||auto_updatekey_isPressed() ) { //自动升级健按下进行自动升级 AUTO_UPDATE_FROM_SD=TRUE; } else { AUTO_UPDATE_FROM_SD = FALSE; } 其中_FAT_Boot_ReadFile()函数用于从SD卡中读取_UPDATE_FILE_DETECT文件,该字符串在最前面有如下定义:
#define _UPDATE_FILE_DETECT "DETECT BIN"
auto_updatekey_isPressed()函数用于检测拍照键是否按下,从这里可以看出,如果SD卡中存在detect.bin文件,则升级时不用再按住拍照键了。如果没有则必须按住拍照键。
Flash格式化的程序如下: if(1)// format flash. { //ADDED BY ZHOUPENG FOR TEST ReadDeviceSerialNumber(Serial_Block_Number,pBuf8); ReadDeviceSerialNumber(Smit_Device_Serial_Block_Nu mber,pBuf9); OALMSG(TRUE, (TEXT(" ++Format FIL (Erase All Blocks)\r\n"))); if (VFL_Close() != VFL_SUCCESS) { OALMSG(TRUE, (TEXT("[ERR] VFL_Close() Failure\r\n"))); } if (WMR_Format_FIL() == FALSE32) { OALMSG(TRUE, (TEXT("[ERR] WMR_Format_FIL() Failure\r\n"))); } if (WMR_Format_VFL() == FALSE32) { OALMSG(TRUE, (TEXT("[ERR] WMR_Format_VFL() Failure\r\n"))); } if (WMR_Format_FTL() == FALSE32) { OALMSG(TRUE, (TEXT("[ERR] WMR_Format_FTL() Failure\r\n"))); } OALMSG(TRUE, (TEXT("[INF] You can not use VFL before Format VFL\r\n"))); OALMSG(TRUE, (TEXT(" --Format FIL (Erase All Blocks)\r\n"))); WriteDeviceSerialNumber(Serial_Block_Number,pBuf8) ; WriteDeviceSerialNumber(Smit_Device_Serial_Block_N umber,pBuf9); } 下载映像的函数如下: static BOOL DownloadImage (LPDWORD pdwImageStart, LPDWORD pdwImageLength, LPDWORD pdwLaunchAddr) { BYTE hdr[BL_HDRSIG_SIZE]; DWORD dwRecLen, dwRecChk, dwRecAddr; BOOL fIsFlash = FALSE; LPBYTE lpDest = NULL; int nPkgNum = 0; BYTE nNumDownloadFiles = 1; DWORD dwImageStart = 0; DWORD dwImageLength = 0; RegionInfo *pCurDownloadFile; int i; *pdwImageStart = *pdwImageLength = *pdwLaunchAddr = 0; g_DownloadManifest.dwNumRegions = 0 ; for( i = 0 ; i read image signature.\r\n"); HALT (BLERR_MAGIC); return (FALSE); } // An N000FF packet is manufactured by Platform Builder when we're // downloading multiple files or when we're downloading a .nb0 file. if (!memcmp (hdr, "N000FF\x0A", BL_HDRSIG_SIZE))//比较数据相等时memcmp返回0 { // read the packet checksum. // if (!OEMReadData (sizeof (DWORD), (LPBYTE) &dwRecChk)) { EdbgOutputDebugString("\r\nUnable to read download manifest checksum.\r\n"); HALT (BLERR_MAGIC); return (FALSE); } // read BIN region descriptions (start address and length). // if (!OEMReadData (sizeof (DWORD), (LPBYTE) &g_DownloadManifest.dwNumRegions) || !OEMReadData ((g_DownloadManifest.dwNumRegions * sizeof(RegionInfo)), (LPBYTE) &g_DownloadManifest.Region[0])) { EdbgOutputDebugString("\r\nUnable to read download manifest information.\r\n"); HALT (BLERR_MAGIC); return (FALSE); } // verify the packet checksum. if (!VerifyChecksum((g_DownloadManifest.dwNumRegions * sizeof(RegionInfo)), (LPBYTE) &g_DownloadManifest.Region[0], dwRecChk)) { EdbgOutputDebugString ("\r\nDownload manifest packet failed checksum verification.\r\n"); HALT (BLERR_CHECKSUM); return (FALSE); } if (g_pOEMMultiBINNotify) { g_pOEMMultiBINNotify((PDownloadManifest)&g_Downloa dManifest); } // look for next download... nNumDownloadFiles = (BYTE)(g_DownloadManifest.dwNumRegions + 1); // +1 to account for this packet. continue; } // Is this an old X000FF multi-bin packet header? It's no longer supported. else if (!memcmp (hdr, "X000FF\x0A", BL_HDRSIG_SIZE)) { EdbgOutputDebugString ("ERROR: The X000FF packet is an old-style multi-bin download manifest and it's no longer supported. \r\nPlease update your Platform Builder installation in you want to download multiple files.\r\n"); HALT (BLERR_MAGIC); return (FALSE); } // Is this a standard bin image? Check for the usual bin file signature. else if (!memcmp (hdr, "B000FF\x0A", BL_HDRSIG_SIZE))//lqm:下载xip.bin,nk.bin,eboot.bin时在这里执行 { g_bBINDownload = TRUE; if (!OEMReadData (sizeof (DWORD), (LPBYTE) &dwImageStart)//读取*.bin前7个头字节后的一个DWORD到dwImageStart。*.bin记录了其映像起始地址 || !OEMReadData (sizeof (DWORD), (LPBYTE) &dwImageLength))//读取上面接着的一个DWORD到dwImageLength。*.bin记录了其映像长度 { EdbgOutputDebugString ("Unable to read image start/length\r\n"); HALT (BLERR_MAGIC); return (FALSE); } } // If the header signature isn't recognized, we'll assume the // download file is a raw .nb0 file. // else { g_bBINDownload = FALSE; } if (!g_DownloadManifest.dwNumRegions) { g_DownloadManifest.dwNumRegions = 1; //声明*.bin有一条记录 g_DownloadManifest.Region[0].dwRegionStart = dwImageStart; //声明这条记录的起始地址 g_DownloadManifest.Region[0].dwRegionLength = dwImageLength;//声明这条记录的映像长度 // Provide the download manifest to the OEM. if (g_pOEMMultiBINNotify) { g_pOEMMultiBINNotify((PDownloadManifest)&g_Downloa dManifest); } } // Locate the current download manifest entry (current download file). pCurDownloadFile = &g_DownloadManifest.Region[g_DownloadManifest.dwNu mRegions - nNumDownloadFiles];//当前下载文件指向*.bin开始 // give the OEM a chance to verify memory.内存校验 if (g_pOEMVerifyMemory && !g_pOEMVerifyMemory (pCurDownloadFile->dwRegionStart, pCurDownloadFile->dwRegionLength)) { EdbgOutputDebugString ("!OEMVERIFYMEMORY: Invalid image\r\n");//lqm:很多情况下都会执行到这里!映像不能获得? HALT (BLERR_OEMVERIFY); return (FALSE); } // check for flash image. Start erasing if it is. if ( (fIsFlash = OEMIsFlashAddr(pCurDownloadFile->dwRegionStart))//l qm:校验起始地址是否在Flash相应地址区域,如果是则擦除相应下载区域 && !OEMStartEraseFlash (pCurDownloadFile->dwRegionStart, pCurDownloadFile->dwRegionLength)) { EdbgOutputDebugString ("Invalid Flash Address/Length\r\n"); HALT (BLERR_FLASHADDR); return (FALSE); } // if we're downloading a binary file, we've already downloaded part of the image when searching // for a file header. copy what we've read so far to the destination buffer, then finish downloading. if (!g_bBINDownload) { lpDest = OEMMapMemAddr (pCurDownloadFile->dwRegionStart, pCurDownloadFile->dwRegionStart); memcpy(lpDest, hdr, BL_HDRSIG_SIZE); // complete the file download... // read data block if (!OEMReadData ((pCurDownloadFile->dwRegionLength - BL_HDRSIG_SIZE), (lpDest + BL_HDRSIG_SIZE))) { EdbgOutputDebugString ("ERROR: failed when reading raw binary file.\r\n"); HALT (BLERR_CORRUPTED_DATA); return (FALSE); } } // we're downloading a .bin file - download each .bin record in turn... else//开始下载*.bin文件到DRAM中。 { while (OEMReadData (sizeof (DWORD), (LPBYTE) &dwRecAddr) && //读取*.bin的第n条记录的地址. OEMReadData (sizeof (DWORD), (LPBYTE) &dwRecLen) && //读取*.bin的第n条记录的长度. OEMReadData (sizeof (DWORD), (LPBYTE) &dwRecChk)) //读取*.bin的第n条记录的校验和. { // last record of .bin file uses sentinel values for address and checksum. // 最后一条记录表示结束.其地址和校验和均为0. if (!dwRecAddr && !dwRecChk) { break;//读到最后一条记录时跳出循环 } // map the record address (FLASH data is cached, for example) // 将存入的记录地址映射到指针地址lpDest lpDest = OEMMapMemAddr (pCurDownloadFile->dwRegionStart, dwRecAddr); // read data block if (!OEMReadData (dwRecLen, lpDest))//读取*.bin的第n条记录的内容到lpDest.这里先把*.bin的数据读到DRAM中。 { EdbgOutputDebugString ("****** Data record %d corrupted, ABORT!!! ******\r\n", nPkgNum); HALT (BLERR_CORRUPTED_DATA); return (FALSE); } if (!VerifyChecksum (dwRecLen, lpDest, dwRecChk))//每读取一条记录则校验一次数据 { EdbgOutputDebugString ("****** Checksum failure on record %d, ABORT!!! ******\r\n", nPkgNum); HALT (BLERR_CHECKSUM); return (FALSE); } // Look for ROMHDR to compute ROM offset. NOTE: romimage guarantees that the record containing // the TOC signature and pointer will always come before the record that contains the ROMHDR contents. // if (dwRecLen == sizeof(ROMHDR) && (*(LPDWORD) OEMMapMemAddr(pCurDownloadFile->dwRegionStart, pCurDownloadFile->dwRegionStart + ROM_SIGNATURE_OFFSET) == ROM_SIGNATURE)) { DWORD dwTempOffset = (dwRecAddr - *(LPDWORD)OEMMapMemAddr(pCurDownloadFile->dwRegionS tart, pCurDownloadFile->dwRegionStart + ROM_SIGNATURE_OFFSET + sizeof(ULONG))); ROMHDR *pROMHdr = (ROMHDR *)lpDest; // Check to make sure this record really contains the ROMHDR. // if ((pROMHdr->physfirst == (pCurDownloadFile->dwRegionStart - dwTempOffset)) && (pROMHdr->physlast == (pCurDownloadFile->dwRegionStart - dwTempOffset + pCurDownloadFile->dwRegionLength)) && (DWORD)(HIWORD(pROMHdr->dllfirst dlllast) && (DWORD)(LOWORD(pROMHdr->dllfirst dlllast)) { g_dwROMOffset = dwTempOffset; EdbgOutputDebugString("rom_offset=0x%x.\r\n", g_dwROMOffset); } } // verify partial checksum OEMShowProgress (nPkgNum ++); // lqm added Progress in LCD.10-01-22 while( nPkgNum>=8 ) { nPkgNum = 0; LcdDrawString(">"); } // end added. } } if (g_bBINDownload) { // Does this .bin file contain a TOC? if (*(LPDWORD) OEMMapMemAddr(pCurDownloadFile->dwRegionStart, pCurDownloadFile->dwRegionStart + ROM_SIGNATURE_OFFSET) == ROM_SIGNATURE) //+ ksk 20051219 { // Contain the kernel? if (IsKernelRegion(pCurDownloadFile->dwRegionStart, pCurDownloadFile->dwRegionLength)) { *pdwImageStart = pCurDownloadFile->dwRegionStart; *pdwImageLength = pCurDownloadFile->dwRegionLength; *pdwLaunchAddr = dwRecLen; } } // No TOC - not made by romimage. However, if we're downloading more than one // .bin file, it's probably chain.bin which doesn't have a TOC (and which isn't // going to be downloaded on its own) and we should ignore it. // else if (g_DownloadManifest.dwNumRegions == 1) { *pdwImageStart = pCurDownloadFile->dwRegionStart; *pdwImageLength = pCurDownloadFile->dwRegionLength; *pdwLaunchAddr = dwRecLen; } } else // Raw binary file. { *pdwImageStart = pCurDownloadFile->dwRegionStart; *pdwLaunchAddr = pCurDownloadFile->dwRegionStart; *pdwImageLength = pCurDownloadFile->dwRegionLength; } // write to flash if it's flash image if (fIsFlash) { // finish the flash erase if (!OEMFinishEraseFlash ()) { HALT (BLERR_FLASH_ERASE); return (FALSE); } // Before writing the image to flash, optionally check the image signature. if (g_bBINDownload && g_pOEMCheckSignature) { if (!g_pOEMCheckSignature(pCurDownloadFile->dwRegionSt art, g_dwROMOffset, *pdwLaunchAddr, TRUE)) HALT(BLERR_CAT_SIGNATURE); } } } while (--nNumDownloadFiles); if (fIsFlash) { nNumDownloadFiles = (BYTE)g_DownloadManifest.dwNumRegions; while (nNumDownloadFiles--) { if (!OEMWriteFlash (g_DownloadManifest.Region[nNumDownloadFiles].dwRe gionStart, g_DownloadManifest.Region[nNumDownloadFiles].dwReg ionLength)) { HALT (BLERR_FLASH_WRITE); return (FALSE); } } } return (TRUE); } 上面程序中有如下代码:
OEMShowProgress (nPkgNum ++);
while( nPkgNum>=8 )
{
nPkgNum = 0;
LcdDrawString(">");
}
该程序放在反复从SD卡中读取xip.bin的一条条记录的while循环中,这里是为了在升级界面上显示升级的进度。LcdDrawString(">")函数是调用了LcdDraw.c中的函数,用于在LCD上绘图。
这里一定要了解xip.bin的文件格式,否则很难理解整个数据流的流程。
Xip.bin以及NK.bin的组成:
组成:标记(7)+Image开始地址(1)+Image长度(1)
记录0地址+记录0长+记录0校验和+记录0内容(文件内容)
记录1地址+记录1长+记录1校验和+记录1内容(文件内容)
......
结束符号
从上面的程序可以看出,
if (!OEMReadData (BL_HDRSIG_SIZE, hdr))
{
EdbgOutputDebugString ("\r\nUnable to read image signature.\r\n");
HALT (BLERR_MAGIC);
return (FALSE);
}
即是将xip.bin的前7个字节的数据读到数组hdr,供后面分析所读的文件的类型。
if (!OEMReadData (sizeof (DWORD), (LPBYTE) &dwImageStart)//读取*.bin前7个头字节后的一个DWORD到dwImageStart。*.bin记录了其映像起始地址
|| !OEMReadData (sizeof (DWORD), (LPBYTE) &dwImageLength))//读取上面接着的一个DWORD到dwImageLength。*.bin记录了其映像长度
{
EdbgOutputDebugString ("Unable to read image start/length\r\n");
HALT (BLERR_MAGIC);
return (FALSE);
}
则是将紧接着前7个字节的两个WORD数据读到dwImageStart和dwImageLength。随后的
while (OEMReadData (sizeof (DWORD), (LPBYTE) &dwRecAddr) &&
//读取*.bin的第n条记录的地址.
OEMReadData (sizeof (DWORD), (LPBYTE) &dwRecLen) &&
//读取*.bin的第n条记录的长度.
OEMReadData (sizeof (DWORD), (LPBYTE) &dwRecChk))
//读取*.bin的第n条记录的校验和.
则开始一条条的解析xip.bin的记录。
if (!OEMReadData (dwRecLen, lpDest))
{
EdbgOutputDebugString ("****** Data record %d corrupted, ABORT!!! ******\r\n", nPkgNum);
HALT (BLERR_CORRUPTED_DATA);
return (FALSE);
}
用于将读取到的第N条记录存放到lpDest.
分析要下载的文件并写入flash的代码如下: void OEMLaunch( DWORD dwImageStart, DWORD dwImageLength, DWORD dwLaunchAddr, const ROMHDR *pRomHdr ) { DWORD dwPhysLaunchAddr; OALMSG(1, (TEXT("+OEMLaunch.\r\n"))); // If the user requested that a disk image (stored in RAM now) be written to the SmartMedia card, so it now. if (g_bDownloadImage) // && (g_pBootCfg->ConfigFlags & TARGET_TYPE_NAND)) { // Since this platform only supports RAM images, the image cache address is the same as the image RAM address. switch (g_ImageType) { case IMAGE_TYPE_STEPLDR://stepldr.bin if (!WriteRawImageToBootMedia(dwImageStart, dwImageLength, dwLaunchAddr)) { OALMSG(OAL_ERROR, (TEXT("ERROR: OEMLaunch: Failed to store image to Smart Media.\r\n"))); goto CleanUp; } OALMSG(TRUE, (TEXT("INFO: Step loader image stored to Smart Media. Please Reboot. Halting...\r\n"))); if (g_bAutoDownload == TRUE) { return ; } while(1); break; case IMAGE_TYPE_LOADER://eboot.bin 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 0 // don't write TOC after download Eboot if ( !TOC_Write() ) { EdbgOutputDebugString("*** OEMLaunch ERROR: TOC_Write failed! Next boot may not load from disk *** \r\n"); } TOC_Print(); #endif // by hmseo - 061123 } OALMSG(TRUE, (TEXT("INFO: Eboot image stored to Smart Media. Please Reboot. Halting...\r\n"))); if (g_bAutoDownload == TRUE) { return ; } while(1); break; case IMAGE_TYPE_RAMIMAGE://NK.bin OALMSG(1, (TEXT("+IMAGE_TYPE_RAMIMAGE dwImageStart:%08x.,dwLaunchAddr : %08x\r\n"),dwImageStart,dwLaunchAddr)); g_pTOC->id[g_dwTocEntry].dwLoadAddress = dwImageStart; g_pTOC->id[g_dwTocEntry].dwTtlSectors = FILE_TO_SECTOR_SIZE(dwImageLength); // 将NK.bin写到flash. if (!WriteOSImageToBootMedia(dwImageStart, dwImageLength, dwLaunchAddr)) { OALMSG(OAL_ERROR, (TEXT("ERROR: OEMLaunch: Failed to store image to Smart Media.\r\n"))); goto CleanUp; } EdbgOutputDebugString("INFO1:dwLaunchAdd : %08x g_pTOC->id[0].dwJumpAddress :%08x, g_pTOC->id[1].dwJumpAddress :%08x\r \r\n", dwLaunchAddr, g_pTOC->id[0].dwJumpAddress,g_pTOC->id[1].dwJumpAddr ess); if (dwLaunchAddr && (g_pTOC->id[g_dwTocEntry].dwJumpAddress != dwLaunchAddr)) { g_pTOC->id[g_dwTocEntry].dwJumpAddress = dwLaunchAddr; if ( !TOC_Write() ) //写TOC文件 { EdbgOutputDebugString("*** OEMLaunch ERROR: TOC_Write failed! Next boot may not load from disk *** \r\n"); } //masked by denis_wei for test 2009-02-05 TOC_Print(); } else { dwLaunchAddr= g_pTOC->id[g_dwTocEntry].dwJumpAddress; EdbgOutputDebugString("INFO2: using TOC[%d] dwJumpAddress: 0x%x\r\n", g_dwTocEntry, dwLaunchAddr); } break; } if(ERASE_ALL_FLASH_BLOCKS)//lqm added.10-01-22 { LcdSetStringPosition(1,15);// lqm added.10-01-22 LcdDrawString("Starting WINCE,Please Wait......");// lqm added.10-01-22 //LcdSetStringPosition(1,16);// lqm added.10-01-22 } if ( !ReadOSImageFromBootMedia( ) )//从FLASH读取映像到DRAM. { OALMSG(OAL_ERROR, (TEXT("OEMPlatformInit ERROR: Failed to load kernel region into RAM.\r\n"))); SpinForever(); } } OALMSG(1, (TEXT("waitforconnect\r\n"))); 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 the MMU off)... // dwPhysLaunchAddr = (DWORD)OALVAtoPA((void *)dwLaunchAddr); OALMSG(TRUE, (TEXT("INFO: OEMLaunch: Jumping to Physical Address 0x%Xh (Virtual Address 0x%Xh)...\r\n\r\n\r\n"), dwPhysLaunchAddr, dwLaunchAddr)); // Jump... if( g_bAutoDownload == TRUE ) { OALMSG(TRUE, (TEXT("INFO: auto update (BLOCK0IMAGE EBOOT NK) SUCCESS!\r\n")));} Launch(dwPhysLaunchAddr);//跳到指定物理地址运行程序. CleanUp: OALMSG(TRUE, (TEXT("ERROR: OEMLaunch: Halting...\r\n"))); SpinForever(); } 在上面代码中,如下代码用于将已经读入DRAM的数据烧写到flash. if (!WriteOSImageToBootMedia(dwImageStart, dwImageLength, dwLaunchAddr)) { OALMSG(OAL_ERROR, (TEXT("ERROR: OEMLaunch: Failed to store image to Smart Media.\r\n"))); goto CleanUp; } 整个bootloader代码量大,但是细下心来慢慢分析,其实不难理解。这里就不一一分析了。