From 63f206cd23f49815a74dcc81f908d8e586f727a3 Mon Sep 17 00:00:00 2001 From: Javier Viguera Date: Wed, 16 Jan 2013 16:00:11 +0100 Subject: [PATCH] meta-digi-del: add 'update_flash' application Signed-off-by: Javier Viguera --- .../packagegroups/packagegroup-del-core.bb | 1 + .../recipes-digi/update-flash/update-flash.bb | 25 + .../update-flash/update-flash/jffs2-user.h | 33 + .../update-flash/update-flash/update_flash.c | 1772 +++++++++++++++++ 4 files changed, 1831 insertions(+) create mode 100644 meta-digi-del/recipes-digi/update-flash/update-flash.bb create mode 100644 meta-digi-del/recipes-digi/update-flash/update-flash/jffs2-user.h create mode 100644 meta-digi-del/recipes-digi/update-flash/update-flash/update_flash.c diff --git a/meta-digi-del/recipes-core/packagegroups/packagegroup-del-core.bb b/meta-digi-del/recipes-core/packagegroups/packagegroup-del-core.bb index 4458c7346..700fbc64b 100644 --- a/meta-digi-del/recipes-core/packagegroups/packagegroup-del-core.bb +++ b/meta-digi-del/recipes-core/packagegroups/packagegroup-del-core.bb @@ -40,6 +40,7 @@ RDEPENDS_${PN} = "\ ${VIRTUAL-RUNTIME_init_manager} \ ${VIRTUAL-RUNTIME_dev_manager} \ ${VIRTUAL-RUNTIME_update-alternatives} \ + update-flash \ usbutils \ ${MACHINE_ESSENTIAL_EXTRA_RDEPENDS}" diff --git a/meta-digi-del/recipes-digi/update-flash/update-flash.bb b/meta-digi-del/recipes-digi/update-flash/update-flash.bb new file mode 100644 index 000000000..45bbe5efb --- /dev/null +++ b/meta-digi-del/recipes-digi/update-flash/update-flash.bb @@ -0,0 +1,25 @@ +SUMMARY = "Digi's update test utility" +SECTION = "base" +LICENSE = "GPL-2.0" +LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/GPL-2.0;md5=801f80980d171dd6425610833a22dbe6" + +PR = "r0" + +DEPENDS += "libdigi" + +SRC_URI = "file://update_flash.c \ + file://jffs2-user.h \ + " + +GIT_SHA1 = "$(cd ${THISDIR} && git rev-parse --short HEAD)" + +S = "${WORKDIR}" + +do_compile() { + ${CC} -O2 -Wall -DGIT_SHA1=\"${GIT_SHA1}\" update_flash.c -o update_flash -ldigi +} + +do_install() { + install -d ${D}${bindir} + install -m 0755 update_flash ${D}${bindir} +} diff --git a/meta-digi-del/recipes-digi/update-flash/update-flash/jffs2-user.h b/meta-digi-del/recipes-digi/update-flash/update-flash/jffs2-user.h new file mode 100644 index 000000000..001685d7f --- /dev/null +++ b/meta-digi-del/recipes-digi/update-flash/update-flash/jffs2-user.h @@ -0,0 +1,33 @@ +/* + * JFFS2 definitions for use in user space only + */ + +#ifndef __JFFS2_USER_H__ +#define __JFFS2_USER_H__ + +/* This file is blessed for inclusion by userspace */ +#include +#include +#include + +#undef cpu_to_je16 +#undef cpu_to_je32 +#undef cpu_to_jemode +#undef je16_to_cpu +#undef je32_to_cpu +#undef jemode_to_cpu + +extern int target_endian; + +#define t16(x) ({ uint16_t __b = (x); (target_endian==__BYTE_ORDER)?__b:bswap_16(__b); }) +#define t32(x) ({ uint32_t __b = (x); (target_endian==__BYTE_ORDER)?__b:bswap_32(__b); }) + +#define cpu_to_je16(x) ((jint16_t){t16(x)}) +#define cpu_to_je32(x) ((jint32_t){t32(x)}) +#define cpu_to_jemode(x) ((jmode_t){t32(x)}) + +#define je16_to_cpu(x) (t16((x).v16)) +#define je32_to_cpu(x) (t32((x).v32)) +#define jemode_to_cpu(x) (t32((x).m)) + +#endif /* __JFFS2_USER_H__ */ diff --git a/meta-digi-del/recipes-digi/update-flash/update-flash/update_flash.c b/meta-digi-del/recipes-digi/update-flash/update-flash/update_flash.c new file mode 100644 index 000000000..34cfc2e0d --- /dev/null +++ b/meta-digi-del/recipes-digi/update-flash/update-flash/update_flash.c @@ -0,0 +1,1772 @@ +/* + * update_flash.c + * + * Copyright (C) 2006 by Digi International Inc. + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ +/* + * + * !Revision: $Revision: 1.25 $ + * !Descr: Flash Test Util + * !References: [1] mtd-utils/flash_eraseall.c + * [2] http://www.linux-mtd.infradead.org/doc/nand.html + * [3] http://www.linux-mtd.infradead.org/tech/mtdnand/x255.html + */ + +#define _XOPEN_SOURCE 500 /* for pread/pwrite */ + +#include /* ENOENT */ +#include /* open */ +#include /* basename */ +#include /* regexp */ +#include /* kill */ +#include /* varg */ +#include /* printf */ +#include /* EXIT_SUCCESS */ +#include /* memset */ +#include /* close */ + +#include /* ntohl */ +#include /* setmntent */ +#include /* ioctl */ +#include /* mount */ +#include /* stat */ +#include /* statfs */ + +#include /* MEMERASE */ + +static int target_endian = __BYTE_ORDER; /* for jffs2-user.h, cpu_to_je16 */ +#include "jffs2-user.h" /* jffs2_unknown_node */ + +/* libdigi */ +#include +#include +#include +#include +#include +#include + +#define VERSION "1.25" "-g"GIT_SHA1 + +/* man statfs does mention them, but they are only defined inside kernel */ +#define NFS_SUPER_MAGIC 0x6969 /* linux/nfs_fs.h */ +#define JFFS2_SUPER_MAGIC 0x72b6 /* linux/jffs2.h */ + +#define IO_BLOCK_SIZE 65536 + +#define FLASH_ERASED_BYTE 0xff + +#define WARNING "!!! " +#define INFO "--- " + +#define PRINTF(...) \ + do { \ + if (!cSilent) \ + printf(__VA_ARGS__); \ + } while (0) + +#define PERCENTAGE(iCurrent, iTotal) ((iCurrent * 100) / (iTotal ? iTotal : 1)) + +#define SET_CRC32(pMtd, uiCRC32) \ + do { \ + pMtd->uiCRC32 = uiCRC32; \ + pMtd->cChecksumSet = 1; \ + } while (0) + +/* open it at least read-only so we can see whether open fails or not */ +#define OPEN_READWRITE_IF_NOT_DRY ((cDryRun ? O_RDONLY : O_RDWR) | O_SYNC) + +/* ********** data types ********** */ + +typedef enum { + PTUBoot = 0, + PTKernel, + PTEnvironment, + PTFPGA, + PTBootstream, + PTUnknown /* always last */ +} PartType_e; + +typedef enum { + FTUBoot = 0, + FTKernel, + FTFPGA, + FTNVRAM, + FTJFFS2, + FTSQUASHFS, + FTUBI, + FTBootstream, + FTUnknown /* always last */ +} FileType_e; + +typedef struct { + /* configuration data */ + const char *szOrigImageFileName; + const char *szImageFileName; /* may later be /tmp/ */ + unsigned int uiPartition; + char cChecksumSet; + char cEraseAll; + uint32_t uiCRC32; + + /* auto-detected */ + char cChecksumCalculated; + char acName[64]; + char cIsNAND; + char cWriteCleanMarker; + char cFileType; + char cIsJFFS2; + loff_t iSize; + loff_t iFileSize; + size_t iPageSize; + mtd_info_t xInfo; + unsigned int uiBadBlocks; + PartType_e ePartType; + FileType_e eFileType; + FileType_e eFileTypeNeeded; + + /* open handles */ + int iFd; /* of Partition */ + + /* status */ + char cAlreadyPrintedVerifyWarning; + char cAlreadyRemounted; + + unsigned int uiClMPos; + unsigned int uiClMLen; + struct jffs2_unknown_node xCleanMarker; +} mtdPartition_t; + + +/* ********** function definitions ********** */ + +/* top level functions */ +static void DoPrintChecksums(void); +static void DoMtdUpdate(void); +static void DoMtdVerify(void); + +static void OnExit(void); + +/* helper functions */ +static void MtdInit(void); +static void MtdPartInit( /*@out@ */ mtdPartition_t * pMtd, unsigned int uiPartition, + const char *szImageFileName); +static void MtdPartOpen( /*@inout@ */ mtdPartition_t * pMtd, char cReadOnly); +static void MtdPartClose( /*@inout@ */ mtdPartition_t * pMtd); +static int MtdPartIsBadBlock(const mtdPartition_t * pMtd, loff_t iOffset); +static void MtdPartUseFile( /*@inout@ */ mtdPartition_t * pMtd, const char *szImageFileName); +static void MtdPartErase(const mtdPartition_t * pMtd); +static void MtdPartWrite(mtdPartition_t * pMtd); +static int MtdPartVerify(mtdPartition_t * pMtd); +static void MtdPartCheckCRC32(mtdPartition_t * pMtd); +static void MtdPartCopyFile(mtdPartition_t * pMtd, const char *szDstFileName); +static void MtdPartInitCleanMarker(const mtdPartition_t * pMtd, + struct jffs2_unknown_node *pCleanMarker, + unsigned int *puiClMPos, unsigned int *puiClMLen); +static void MtdPartInitJFFS2Node(struct jffs2_unknown_node *pNode, unsigned short uhNodeType, + size_t iLen); +static void MtdPartCompareCRC32(mtdPartition_t * pMtd, uint32_t uiCRC32); +static void MtdPartRemountAllReadOnly(mtdPartition_t * pMtd); +static void MtdPartDeterminePartType(mtdPartition_t * pMtd); +static void MtdPartDetermineAndCheckFileType(mtdPartition_t * pMtd); +static void MtdPartVerifyFile(mtdPartition_t * pMtd); +static void MtdPartVerifyJFFS2Block(mtdPartition_t * pMtd, unsigned char *pucData, + size_t iSize); +static int MtdPartGetThrottle(const mtdPartition_t * pMtd, uint64_t ullSize); +static void PrintProgress(int iPercentage, int iThrottle, const char *szFmt, ...); +static void VerifyTmpDir(void); +static uint32_t CalcCRC32OfFile(const char *szFileName); +static const char *GetRootDevice(void); +static void mtd_part_write_ubi(mtdPartition_t * pMtd); + +/* ********** local variables ********** */ + +/* set by command line */ +static const char *szTmpDir = NULL; +static const char *szKey = NULL; +static char cNoImageTypeCheck = 0; +static char cProgressInNewLine = 0; +static char cSilent = 0; +static char cChecksumOnly = 0; +static char cDoReboot = 0; +static char cHasChecksum = 0; +static char cWriteCleanMarker = 0; +static char cDryRun = 0; +static char cVerify = 0; +static char cVerifyOnly = 0; +static char cEraseAll = 0; +static char cMaxRetries = 3; +static char cMarkBadBlocks = 0; + +/* calculated */ +static mtdPartition_t axMtdParts[64]; +static mtdPartition_t *pMtdPartLastToUpdate = axMtdParts; +static const char *szMtdPrefix = "/dev/mtd/"; +static const char *szMtdBlockPrefix = "/dev/mtdblock/"; +static unsigned int uiMtdPartsCount = 0; + +#define MK(x, szName)[x] = szName +static const char *aszPartType[PTUnknown + 1] = { + MK(PTUBoot, "U-Boot"), + MK(PTKernel, "Kernel"), + MK(PTEnvironment, "NVRAM"), + MK(PTFPGA, "FPGA"), + MK(PTBootstream, "Bstrm-U-Boot"), + MK(PTUnknown, "Unknown"), +}; +#undef MK + +#define MK(x, szRegExp, szName)[x] = {szRegExp, szName} +static const struct { + const char *szExp; + const char *szName; +} axFileType[FTUnknown + 1] = { + MK(FTUBoot, "u-boot-.*\\.bin", "UBoot"), + MK(FTNVRAM, "nvram-.*", "NVRAM"), + MK(FTKernel, "uImage-.*", "Kernel"), + MK(FTFPGA, ".*\\.biu", "FPGA"), + MK(FTJFFS2, ".*\\.jffs2", "JFFS2"), + MK(FTSQUASHFS, ".*\\.squashfs", "SQUASHFS"), + MK(FTUBI, ".*\\.ubi", "UBI"), + MK(FTBootstream, ".*\\.sb", "Bootstream"), + MK(FTUnknown, ".*", "Unknown"), +}; +#undef MK + +/* ********** function implementations ********** */ + +int main(int argc, char *argv[]) +{ + int iPartListIndex; + + CmdOptEntry aCmdEntries[] = { + {COT_BOOL, 'C', &cChecksumOnly, "checksum-only", + "calculates only CRC32 checksum of image"}, + {COT_BOOL, 'R', &cDoReboot, "reboot", + "reboots the system"}, + {COT_BOOL, 'V', &cVerifyOnly, "verify-only", + "verifies current contents, no updates are done"}, + {COT_BOOL, 'v', &cVerify, "verify", + "After flashing, compare flash contents with image on byte-to-byte"}, + {COT_BOOL, 'c', &cHasChecksum, "checksum", + "flashes only when checksum matches"}, + {COT_BOOL, -1, &cDryRun, "dry-run", + "don't erase or write to the flash"}, + {COT_BOOL, -1, &cProgressInNewLine, "progress-in-new-line", + "each percentage is printed in an own line"}, + {COT_BOOL, 'i', &cNoImageTypeCheck, "no-image-type-check", + "doesn't check image type for partition"}, + {COT_BOOL, 'f', &cEraseAll, "erase-all", + "erases the partition, not only the parts being written"}, + {COT_BOOL, -1, &cWriteCleanMarker, "clean-marker", + "writes clean markers to every partition (implies -f)"}, + {COT_BOOL, 'b' , &cMarkBadBlocks, "bad-block-marking", + "On repeated error, marks block as bad."}, + {COT_BOOL, 's', &cSilent, "silent", + "Silent Mode"}, + {COT_STRING, 't', &szTmpDir, "tmpdir", + "copy files to temporary directory before flashing"}, + {COT_STRING, 'k', &szKey, "encrypt_key", + "Verify bootstream image against encryption key"}, + {COT_MORE, 0, NULL, "", + "file to flash to partition and check for checksum"}, + {COT_NONE, 0, NULL, NULL, NULL}, + }; + + CLEAR(axMtdParts); + + szCmdOptVersion = "Version: " VERSION ", compiled on " __DATE__ "," __TIME__; + iPartListIndex = cmdOptParse(argc, argv, aCmdEntries, + "Flash Update Tool\n\n" + "Examples of use cases:\n" + " update_flash rootfs-ccw9cjsnand-128.jffs2 4\n" + " => updates partition /dev/mtd4 with rootfs image\n" + "\n" + " update_flash -C uImage-ccw9cjsnand\n" + " => calculates file CRC32 only\n" + "\n" + " update_flash -c uImage-ccw9cjsnand 3 0x1051e3c9\n" + " => updates partition /dev/mtd3 only if CRC32 of file is 0x1051e3c9\n" + "\n" + " update_flash uImage-ccw9cjsnand 3 rootfs-ccw9cjsnand-128.jffs2 4:\n" + " => updates kernel at partition 3 and rootfs at partition 4\n" + "\n" + " update_flash u-boot-cpx2-ivt.sb 0 -k 48855699413545113545511513300447\n" + " => updates bootstream file on partition 0 if the encryption key matches\n"); + + /* so we can close everything even on error() or on return of main */ + atexit(OnExit); + + /* Force disable writing clean markers for platforms that require atomic + * access to the OOB */ + if (cWriteCleanMarker && is_nand_oob_atomic()) { + PRINTF(WARNING "JFFS2 clean markers disabled for this platform\n"); + cWriteCleanMarker = 0; + } + + if (cWriteCleanMarker) + cEraseAll = 1; + + if (!cChecksumOnly) + MtdInit(); + + /* check what files to write to what partition */ + while (iPartListIndex < argc) { + unsigned int uiPartition = 0; + + if ((pMtdPartLastToUpdate - axMtdParts) >= ARRAY_SIZE(axMtdParts)) + error("Too many partitions to update on command line"); + + if (iPartListIndex > (argc - (1 + (cChecksumOnly ? 0 : 1) + (cHasChecksum ? 1 : 0)))) + error("Require filename%s%s", (cChecksumOnly ? "" : " and partition"), + (cHasChecksum ? " and checksum" : "")); + + if (!cChecksumOnly) { + /* check partition argument */ + if (sscanf(argv[iPartListIndex + 1], "%u", &uiPartition) != 1) + error("Wrong partition number\n"); + + if (uiPartition >= uiMtdPartsCount) + error("Have only %u mtd partitions", uiMtdPartsCount); + + MtdPartInit(pMtdPartLastToUpdate, uiPartition, argv[iPartListIndex]); + iPartListIndex++; + } else { + /* initialize it partly */ + CLEAR(*pMtdPartLastToUpdate); + pMtdPartLastToUpdate->iFd = -1; + MtdPartUseFile(pMtdPartLastToUpdate, argv[iPartListIndex]); + } + + if (cHasChecksum) { + /* parse checksum argument */ + const char *szCRC32 = argv[iPartListIndex + 1]; + uint32_t uiCRC32; + + if (sscanf(szCRC32, "%x", &uiCRC32) != 1) + error("Invalid Checksum: %s", szCRC32); + SET_CRC32(pMtdPartLastToUpdate, uiCRC32); + iPartListIndex++; + } + + iPartListIndex++; + pMtdPartLastToUpdate++; + } /* while( iPartListIndex ) */ + + /* all command line parsing verifications complete */ + + /* report what will be done */ + if (cNoImageTypeCheck || cDryRun || cWriteCleanMarker || cEraseAll) { + PRINTF("\nEnabled command line options:\n"); + if (cNoImageTypeCheck) + PRINTF(INFO "do not check image type\n"); + if (cDryRun) + PRINTF(INFO "dry-run: flash content is not changed\n"); + if (cWriteCleanMarker) + PRINTF(INFO "write JFFS2 clean markers\n"); + if (cEraseAll) + PRINTF(INFO "erase complete partition\n"); + PRINTF("\n"); + } + + if (cChecksumOnly) + DoPrintChecksums(); + else if (cVerifyOnly) + DoMtdVerify(); + else + DoMtdUpdate(); + + PRINTF("Done\n"); + + if (cDoReboot) { + sync(); + + PRINTF("Rebooting System\n"); + /* kills init */ + kill(1, SIGTERM); + } + + /* may not be reached in case of kill */ + return EXIT_SUCCESS; +} + +/*********************************************************************** + * !Function: DoPrintChecksums + * !Descr: Calculates CRC32 of files (and compares them to -c option) + ***********************************************************************/ +static void DoPrintChecksums(void) +{ + mtdPartition_t *pMtd = NULL; + + PRINTF("CRC32 Results:\n"); + + for (pMtd = axMtdParts; pMtd < pMtdPartLastToUpdate; pMtd++) { + uint32_t uiCRC32 = 0; + + uiCRC32 = CalcCRC32OfFile(pMtd->szImageFileName); + PRINTF(" %-20s : 0x%08x\n", pMtd->szImageFileName, uiCRC32); + + if (pMtd->cChecksumSet) + MtdPartCompareCRC32(pMtd, uiCRC32); + } +} + +/*********************************************************************** + * !Function: DoMtdUpdate + * !Descr: Updates all flash partitions + ***********************************************************************/ +static void DoMtdUpdate(void) +{ + mtdPartition_t *pMtd = NULL; + + if (NULL != szTmpDir) { + /* checksum is checked or calculated while copying to tmp */ + VerifyTmpDir(); + + /* copy all files to tmp before starting to update */ + for (pMtd = axMtdParts; pMtd < pMtdPartLastToUpdate; pMtd++) { + /* copy it to temp */ + char acTmpFileName[256]; + char *szFileName = strdup(pMtd->szImageFileName); + snprintf(acTmpFileName, sizeof(acTmpFileName) - 1, + "%s/%s", szTmpDir, basename(szFileName)); + acTmpFileName[sizeof(acTmpFileName) - 1] = 0; + FREE(szFileName); + + PRINTF("Copying %s to %s\n", pMtd->szImageFileName, szTmpDir); + MtdPartCopyFile(pMtd, acTmpFileName); + + /* use temporary file name from name */ + FREE(pMtd->szImageFileName); /* get rid of const* */ + pMtd->szImageFileName = strdup(acTmpFileName); + } + } else if (cHasChecksum || !cNoImageTypeCheck) { + /* verify checksum before starting to update */ + PRINTF("Verifying File(s): "); + for (pMtd = axMtdParts; pMtd < pMtdPartLastToUpdate; pMtd++) { + PRINTF(" %s\n", pMtd->szImageFileName); + MtdPartVerifyFile(pMtd); + } + } + + /* make it read-only so no one can destroy the data */ + for (pMtd = axMtdParts; pMtd < pMtdPartLastToUpdate; pMtd++) + MtdPartRemountAllReadOnly(pMtd); + + PRINTF("Updating:\n"); + + for (pMtd = axMtdParts; pMtd < pMtdPartLastToUpdate; pMtd++) { + PRINTF(" %s (%lli KiB)\n", pMtd->szImageFileName, TO_KiB(pMtd->iFileSize)); + + MtdPartOpen(pMtd, 0); + /* For UBI images 'ubiformat' takes care of erasing the partition */ + if (pMtd->eFileType != FTUBI) { + MtdPartErase(pMtd); + } + if (PTBootstream == pMtd->ePartType) { + char cmd[1024]; + + /* Bootstream partitions must be updated by Freescale kobs-ng application */ + /* Close open mtd device */ + MtdPartClose(pMtd); + /* Build command to call kobs-ng + * Use 'strcat' because -O2 compiler optimization + * creates problems with 'sprintf' */ + strcpy(cmd, "kobs-ng init -w"); + if (cVerify) + strcat(cmd, " -c"); + if (NULL != szKey) { + strcat(cmd, " -k"); + strcat(cmd, szKey); + } + strcat(cmd, " --chip_0_device_path="); + strcat(cmd, pMtd->acName); + strcat(cmd, " "); + strcat(cmd, pMtd->szImageFileName); + strcat(cmd, " > /dev/null"); + if (!system(cmd)) { + PRINTF("\r Flashing: complete \n"); + if (cVerify) { + PRINTF("\r Verifying: complete \n"); + } + } + else { + PRINTF("\r Flashing: FAILED! \n"); + exit(EXIT_FAILURE); + } + } else if (pMtd->eFileType == FTUBI) { + /* UBI images are flashed using 'ubiformat' command */ + MtdPartClose(pMtd); + mtd_part_write_ubi(pMtd); + } else { + MtdPartWrite(pMtd); + if (cVerify) { + if (!MtdPartVerify(pMtd)) + exit(EXIT_FAILURE); + /* if CRC32 is given, it has been already checked on + the input files. And Mtd is now same to them. So no + need to check CRC32 again. */ + } else if (cHasChecksum) + MtdPartCheckCRC32(pMtd); + MtdPartClose(pMtd); + } + + PRINTF(" CRC32: 0x%08x\n", pMtd->uiCRC32); + } + + if (NULL != szTmpDir) { + /* delete all temporary files */ + for (pMtd = axMtdParts; pMtd < pMtdPartLastToUpdate; pMtd++) + if (unlink(pMtd->szImageFileName)) { + /* + * Do not exit with error in case the file to remove does not + * exist. + * Try to address corner cases like CCORE_MX53_EXTENSIONS-170 + * (using same file to flash several different partitions) + */ + if (ENOENT == errno) { + systemLog("%s", pMtd->szImageFileName); + } else { + systemError("%s", pMtd->szImageFileName); + } + } + } +} + +/*********************************************************************** + * !Function: DoMtdVerify + * !Descr: Verifies the flash images + ***********************************************************************/ +static void DoMtdVerify(void) +{ + mtdPartition_t *pMtd = NULL; + + PRINTF("Verifying Images:\n"); + + for (pMtd = axMtdParts; pMtd < pMtdPartLastToUpdate; pMtd++) { + MtdPartOpen(pMtd, 1); + MtdPartVerify(pMtd); + MtdPartClose(pMtd); + } +} + +/*********************************************************************** + * !Function: OnExit + * !Descr: Closes all open mtd partitions + ***********************************************************************/ +static void OnExit(void) +{ + mtdPartition_t *pMtd; + + for (pMtd = axMtdParts; pMtd < pMtdPartLastToUpdate; pMtd++) { + if (-1 != pMtd->iFd) + MtdPartClose(pMtd); + + FREE(pMtd->szImageFileName); + } +} + +/*********************************************************************** + * !Function: MtdInit + * !Descr: Checks where the partitions (devfs or not) and how many we have + ***********************************************************************/ +static void MtdInit(void) +{ + struct stat xStat; + + CLEAR(xStat); + + /* determine whether we are /dev/mtd/ or /dev/mtd */ + if (-1 == stat(szMtdPrefix, &xStat)) { + /* not dev fs */ + if (-1 == stat("/dev/mtd0", &xStat)) + error("No MTD devices available"); + + szMtdPrefix = "/dev/mtd"; + szMtdBlockPrefix = "/dev/mtdblock"; + } + + /* determine number of partitions */ + while (uiMtdPartsCount < ARRAY_SIZE(axMtdParts)) { + char acName[64]; + + axMtdParts[uiMtdPartsCount].iFd = -1; /* not open yet */ + + sprintf(acName, "%s%u", szMtdPrefix, uiMtdPartsCount); + if (-1 == stat(acName, &xStat)) + break; + + uiMtdPartsCount++; + } + + if (!uiMtdPartsCount) + error("No MTD partitions found"); +} + +/*********************************************************************** + * !Function: MtdPartInit + * !Descr: opens partition and initializes all sizes and bad blocks + ***********************************************************************/ +static void MtdPartInit( /*@out@ */ mtdPartition_t * pMtd, unsigned int uiPartition, + const char *szImageFileName) +{ + CLEAR(*pMtd); + pMtd->iFd = -1; + pMtd->uiPartition = uiPartition; + + /* open mtd (may be /dev/mtd0 or /dev/mtd/0) */ + MtdPartOpen(pMtd, 0); + + /* read partition info */ + if (ioctl(pMtd->iFd, MEMGETINFO, &pMtd->xInfo)) + systemError("ioctl( MEMGETINFO )"); + + pMtd->iSize = pMtd->xInfo.size; + pMtd->cIsNAND = (MTD_NANDFLASH == pMtd->xInfo.type); + pMtd->uiBadBlocks = 0; + pMtd->cEraseAll = cEraseAll; + pMtd->cWriteCleanMarker = cWriteCleanMarker; + + if (pMtd->cIsNAND) { + /* determine bad block count */ + /* !TODO. in 2.6.18 there already exists + mtd_ecc_stats/ECCGETLAYOUT */ + loff_t iOffset = 0; + + while (iOffset < pMtd->xInfo.size) { + if (MtdPartIsBadBlock(pMtd, iOffset)) { + logMsg(LOG_HARDWARE1, + "Bad Block 0x%08llx on partition %u", + iOffset, pMtd->uiPartition); + pMtd->uiBadBlocks++; + } + iOffset += pMtd->xInfo.erasesize; + } + } + + pMtd->iPageSize = (pMtd->cIsNAND ? pMtd->xInfo.writesize : pMtd->xInfo.erasesize); + + MtdPartDeterminePartType(pMtd); + + PRINTF("Partition %u is %s (%s)\n", pMtd->uiPartition, + (pMtd->cIsNAND ? "NAND" : ((MTD_NORFLASH == pMtd->xInfo.type) ? "NOR" : "???")), + (pMtd->ePartType != PTUnknown) ? aszPartType[pMtd->ePartType] : ""); + PRINTF(" Full Size: %llu KiB\n", TO_KiB(pMtd->iSize)); + + if (pMtd->uiBadBlocks) { + /* determine effective (good) size */ + PRINTF(" %u bad blocks\n", pMtd->uiBadBlocks); + pMtd->iSize -= pMtd->xInfo.erasesize * pMtd->uiBadBlocks; + } + + PRINTF(" Good Size: %llu KiB\n", TO_KiB(pMtd->iSize)); + + if (!is_nand_oob_atomic()) + MtdPartInitCleanMarker(pMtd, &pMtd->xCleanMarker, &pMtd->uiClMPos, &pMtd->uiClMLen); + + MtdPartUseFile(pMtd, szImageFileName); + + /* close it to not leave an unused file descriptor open too long. + The partition info is not gonna change anyway. */ + MtdPartClose(pMtd); + + if (!cVerifyOnly && pMtd->cIsJFFS2) { + PRINTF(INFO "JFFS2 partition %u will be fully erased", + pMtd->uiPartition); + /* clean rootfs completely */ + pMtd->cEraseAll = 1; + if (!is_nand_oob_atomic()) { + pMtd->cWriteCleanMarker = 1; + PRINTF(" and clean markers written\n"); + } else { + PRINTF("\n"); + } + } +} + +/*********************************************************************** + * !Function: MtdPartOpen + * !Descr: Opens the partition, either read only or read-writable + ***********************************************************************/ +static void MtdPartOpen( /*@inout@ */ mtdPartition_t * pMtd, char cReadOnly) +{ + char cReallyReadOnly = cReadOnly || cVerifyOnly; + if (-1 != pMtd->iFd) + error("Partition %u already open", pMtd->uiPartition); + + sprintf(pMtd->acName, "%s%u", szMtdPrefix, pMtd->uiPartition); + pMtd->iFd = open(pMtd->acName, (cReallyReadOnly ? O_RDONLY : OPEN_READWRITE_IF_NOT_DRY)); + if (-1 == pMtd->iFd) + systemError(": %s", pMtd->acName); +} + +/*********************************************************************** + * !Function: MtdPartClose + * !Descr: closes the Mtd Partition + ***********************************************************************/ +static void MtdPartClose( /*@inout@ */ mtdPartition_t * pMtd) +{ + CLOSE(pMtd->iFd); +} + +/*********************************************************************** + * !Function: MtdPartIsBadBlock + * !Return: 1 if the block at iOffset is bad and mustn't be used + * !TODO: on first run, all bad blocks can be stored in a "bad" list and + * reused later to reduce kernel calls + ***********************************************************************/ +static int MtdPartIsBadBlock(const mtdPartition_t * pMtd, loff_t iOffset) +{ + char cIsBad = 0; + int iRes = ioctl(pMtd->iFd, MEMGETBADBLOCK, &iOffset); + + if (iRes > 0) + cIsBad = 1; + else if ((iRes < 0) && (ENOTSUP != errno)) + /* if not supported (NOR), assume it is good */ + systemError("ioctl( MEMGETBADBLOCK )"); + + return cIsBad; +} + +/*********************************************************************** + * !Function: MtdMarkBadBlock + * !Return: 0 on success, <1 on error + ***********************************************************************/ +static int MtdMarkBadBlock(const mtdPartition_t * pMtd, loff_t iOffset) +{ + PRINTF("Marking offset %d as bad\n",(int)iOffset); + return ( ioctl(pMtd->iFd, MEMSETBADBLOCK, &iOffset) ); +} + +/*********************************************************************** + * !Function: MtdPartUseFile + * !Descr: Checks whether szImageFileName can be used for updating. + * E.g. if !cNoImageTypeCheck is set, the prefixes of the image + * file names are checked. + ***********************************************************************/ +static void MtdPartUseFile( /*@inout@ */ mtdPartition_t * pMtd, const char *szImageFileName) +{ + struct stat xStat; + + CLEAR(xStat); + if (stat(szImageFileName, &xStat)) + systemError("%s", szImageFileName); + + pMtdPartLastToUpdate->szOrigImageFileName = szImageFileName; + pMtd->szImageFileName = strdup(szImageFileName); + pMtd->iFileSize = xStat.st_size; + pMtd->eFileType = FTUnknown; + + if (-1 != pMtd->iFd) { + if (xStat.st_size > pMtd->iSize) + error("File %s is %lu KiB, but partition %u has only %lu KiB good free", + pMtd->szImageFileName, + TO_KiB(xStat.st_size), pMtd->uiPartition, TO_KiB(pMtd->iSize)); + + if (!cNoImageTypeCheck) + MtdPartDetermineAndCheckFileType(pMtd); + } +} + +/*********************************************************************** + * !Function: MtdPartErase + * !Descr: erases the flash partition. + * !see [1] + ***********************************************************************/ +static void MtdPartErase(const mtdPartition_t * pMtd) +{ + erase_info_t xErase; + loff_t iEraseSize = 0; + loff_t iBytesErasedTotal = 0; + char cLastWasBad = 0; + struct mtd_oob_buf xoob; + int iThrottle = MtdPartGetThrottle(pMtd, pMtd->xInfo.erasesize); + + CLEAR(xErase); + + xErase.length = pMtd->xInfo.erasesize; + /* bad sectors have already been removed in pMtd->iSize */ + iEraseSize = (pMtd->cEraseAll ? pMtd->iSize : pMtd->iFileSize); + + CLEAR(xoob); + xoob.length = pMtd->uiClMLen; + xoob.ptr = (unsigned char *)&pMtd->xCleanMarker; + + while (iBytesErasedTotal < iEraseSize) { + if (!MtdPartIsBadBlock(pMtd, xErase.start)) { + PrintProgress((((iBytesErasedTotal) * 100) / iEraseSize), + iThrottle, + " Erasing%s %i KiB @ 0x%08x:", + (pMtd->cWriteCleanMarker ? " (CM)" : ""), + TO_KiB(xErase.length), xErase.start); + + if (!cDryRun && ioctl(pMtd->iFd, MEMERASE, &xErase)) + systemError("ioctl(MEMERASE)"); + + iBytesErasedTotal += xErase.length; + cLastWasBad = 0; + + if (!cDryRun && pMtd->cWriteCleanMarker) { + /* write cleanmarker */ + if (pMtd->cIsNAND) { + xoob.start = xErase.start + pMtd->uiClMPos; + if (ioctl(pMtd->iFd, MEMWRITEOOB, &xoob)) + systemError("ioctl( MEMWRITEOOB )"); + } else { + /* the NOR image already contains them. */ + if (pwrite(pMtd->iFd, &pMtd->xCleanMarker, + sizeof(pMtd->xCleanMarker), + xErase.start) != sizeof(pMtd->xCleanMarker)) + systemError("pwrite"); + } + } + } else { + logMsg(LOG_HARDWARE1, + "%s" WARNING "Skipping bad sector @ 0x%08x ", + (!cLastWasBad ? "\r" : ""), xErase.start); + cLastWasBad = 1; + } + + xErase.start += xErase.length; + } + + PRINTF("\r Erasing: complete \n"); +} + +/*********************************************************************** + * !Function: mtd_part_write_ubi + * !Descr: writes an UBI image file to partition + ***********************************************************************/ +static void mtd_part_write_ubi(mtdPartition_t * pMtd) +{ + char line[256]; + FILE *fpin; + int ret; + + snprintf(line, sizeof(line), "ubiformat %s -f %s -y -q 2>&1 >/dev/null", pMtd->acName, + pMtd->szImageFileName); + fpin = popen(line, "r"); + if (fgets(line, sizeof(line) - 1, fpin) != NULL) { + line[strlen(line) - 1] = 0; + } + ret = pclose(fpin); + if (!WEXITSTATUS(ret)) { + PRINTF("\r Flashing: complete \n"); + } else { + PRINTF("\r Flashing: FAILED! (%s)\n", line); + exit(EXIT_FAILURE); + } +} + +/*********************************************************************** + * !Function: MtdPartWrite + * !Descr: writes the image file to partition + ***********************************************************************/ +static void MtdPartWrite(mtdPartition_t * pMtd) +{ + loff_t iBytesReadTotal = 0; + loff_t iCurrentOffs = 0; + int iFdSrc = -1; + char cLastWasBad = 0; + int iBytesRead = 0; + uint32_t uiCRC32 = 0; + unsigned char *pucBuffer = NULL; + const size_t iBlockSize = pMtd->iPageSize; + int iThrottle = MtdPartGetThrottle(pMtd, pMtd->iFileSize); + unsigned int i,iRet; + unsigned int ref_uicrc32 = 0,new_uicrc32 = 0; + unsigned char *pucBufferMtd = NULL; + + pucBuffer = (unsigned char *)malloc(iBlockSize); + if (NULL == pucBuffer) + systemError("malloc"); + + iFdSrc = open(pMtd->szImageFileName, O_RDONLY); + if (-1 == iFdSrc) + systemError("%s", pMtd->szImageFileName); + + /* rewind because descriptor was open and we don't know the state */ + if (lseek(pMtd->iFd, 0, SEEK_SET)) + systemError("%s", pMtd->acName); + + do { + /* write one sector */ + if (!MtdPartIsBadBlock(pMtd, iCurrentOffs)) { + int iBytesWritten; + + PrintProgress(PERCENTAGE(iBytesReadTotal, pMtd->iFileSize), + iThrottle, " Flashing:"); + + iBytesRead = read(iFdSrc, pucBuffer, iBlockSize); + if (!iBytesRead) + break; + else if (-1 == iBytesRead) + systemError("%s", pMtd->szImageFileName); + + if (!pMtd->cChecksumCalculated) + uiCRC32 = crc32(uiCRC32, pucBuffer, iBytesRead); + + if (iBytesRead < iBlockSize) { + char cEmptyChar = FLASH_ERASED_BYTE; + char bJFFS2Padding = 0; + + if (pMtd->cIsJFFS2 && + (iBytesRead + sizeof(struct jffs2_unknown_node) < + iBlockSize)) { + bJFFS2Padding = 1; + cEmptyChar = 0; /* see wbuf.c */ + } + + /* fill block with empty characters */ + memset(pucBuffer + iBytesRead, + cEmptyChar, iBlockSize - iBytesRead); + + if (bJFFS2Padding) { + /* write padding to avoid Empty block messages. + see linux/fs/jffs2/wbuf.c:flush_wbuf */ + struct jffs2_unknown_node *pNode = + (struct jffs2_unknown_node *)(pucBuffer + + iBytesRead); + logMsg(LOG_HARDWARE1, "\nPadding last sector"); + MtdPartInitJFFS2Node(pNode, + JFFS2_NODETYPE_PADDING, + iBlockSize - iBytesRead); + } + } + + /* at least nand writes should be aligned */ + + if( !cDryRun && cMarkBadBlocks ) { + ref_uicrc32 = crc32(0, pucBuffer, iBlockSize); + pucBufferMtd = (unsigned char *)malloc(iBlockSize); + if (NULL == pucBufferMtd) + systemError("malloc"); + } + + for( i = 0 ; i < cMaxRetries ; i++ ) { + int iBytesReadMtd; + + iBytesWritten = (!cDryRun ? + pwrite(pMtd->iFd, + pucBuffer, iBlockSize, + iCurrentOffs) : iBlockSize); + + if (iBytesWritten != iBlockSize) { + if( !cDryRun && cMarkBadBlocks ) { + PRINTF("[%s:%d] %s: Retrying failed write %d.\n", + __FUNCTION__,__LINE__,pMtd->acName,i); + continue; + } + else { + systemError("%s", pMtd->acName); + } + } + + if (!cDryRun && cMarkBadBlocks) { + iBytesReadMtd = pread(pMtd->iFd, pucBufferMtd, iBlockSize, + iCurrentOffs); + if ( iBytesReadMtd < 0 ) { + PRINTF("[%s:%d] %s: Read error.\n", + __FUNCTION__,__LINE__,pMtd->acName); + continue; + } + + new_uicrc32 = crc32(0, pucBufferMtd, iBlockSize); + if( new_uicrc32 != ref_uicrc32 ) { + PRINTF("[%s:%d] %s: CRC mismatch %08x <> %08x.\n", + __FUNCTION__,__LINE__,pMtd->acName,ref_uicrc32, + new_uicrc32); + continue; + } + FREE(pucBufferMtd); + } + break; + } + + if( !cDryRun && cMarkBadBlocks && (i >= cMaxRetries) ) { + PRINTF("[%s:%d] %s: Marking as bad block.\n", + __FUNCTION__,__LINE__,pMtd->acName); + iRet = MtdMarkBadBlock( pMtd , iCurrentOffs ); + systemError("%s: Bad block marking %s.", pMtd->acName, + strerror(iRet)); + } + + iBytesReadTotal += iBytesRead; + + cLastWasBad = 0; + } else { + logMsg(LOG_HARDWARE1, + "%s" WARNING "Skipping bad sector @ 0x%08x ", + (!cLastWasBad ? "\r" : ""), iCurrentOffs); + + cLastWasBad = 1; + } + + iCurrentOffs += iBlockSize; + } while (iCurrentOffs < pMtd->xInfo.size); + + CLOSE(iFdSrc); + FREE(pucBuffer); + + if (pMtd->iFileSize != iBytesReadTotal) + error("Filesize changed while updating: %s", pMtd->szImageFileName); + + if (!pMtd->cChecksumCalculated) { + SET_CRC32(pMtd, uiCRC32); + pMtd->cChecksumCalculated = 1; + } + + PRINTF("\r Flashing: complete \n"); +} + +/*********************************************************************** + * !Function: MtdPartVerify + * !Return: 1 if identical in the used sectors of szOrigImageFileName + * otherwise 0 + * !Descr: compares contents of MtdPartition with szOrigImageFileName + * Only checks the last used sector whether the parts the source + * file not uses are empty. Other blocks are not checked. + ***********************************************************************/ +static int MtdPartVerify(mtdPartition_t * pMtd) +{ + unsigned char *pucBufferSrc; + unsigned char *pucBufferMtd; + int iFdSrc = -1; + loff_t iBytesReadTotal = 0; + loff_t iOffsMtd = 0; + char cRes = 0; + char cLastWasBad = 0; + const size_t iBlockSize = pMtd->iPageSize; + int iThrottle = MtdPartGetThrottle(pMtd, pMtd->iFileSize); + unsigned int i,iRet; + + pucBufferSrc = (unsigned char *)malloc(iBlockSize); + if (NULL == pucBufferSrc) + systemError("malloc"); + + pucBufferMtd = (unsigned char *)malloc(iBlockSize); + if (NULL == pucBufferMtd) + systemError("malloc"); + + /* sync everything */ + MtdPartClose(pMtd); + MtdPartOpen(pMtd, 1); /* also rewinds read pointer */ + + iFdSrc = open(pMtd->szOrigImageFileName, O_RDONLY); + if (-1 == iFdSrc) + systemError("%s", pMtd->szOrigImageFileName); + + do { + if (!MtdPartIsBadBlock(pMtd, iOffsMtd)) { + int iBytesReadSrc; + int iBytesReadMtd; + loff_t iOffs; + + PrintProgress(PERCENTAGE(iBytesReadTotal, pMtd->iFileSize), + iThrottle, " Verifying:"); + + iBytesReadSrc = read(iFdSrc, pucBufferSrc, iBlockSize); + if (!iBytesReadSrc) + /* nothing left */ + break; + else if (iBytesReadSrc < 0) + systemError("%s", pMtd->szOrigImageFileName); + + for( i = 0 ; i < cMaxRetries ; i++ ) { + iBytesReadMtd = pread(pMtd->iFd, pucBufferMtd, iBlockSize, iOffsMtd); + if (iBytesReadMtd < 0) { + if( !cDryRun && cMarkBadBlocks ) { + printf("[%s:%d] %s: Retrying failed read %d, %s.\n", + __FUNCTION__,__LINE__,pMtd->acName,i,strerror(errno)); + continue; + } + else + systemError("%s", pMtd->acName); + } + break; + } + + if( !cDryRun && cMarkBadBlocks && (i >= cMaxRetries) ) { + PRINTF("[%s:%d] %s: Marking as bad block.\n", + __FUNCTION__,__LINE__,pMtd->acName); + iRet = MtdMarkBadBlock( pMtd , iOffsMtd ); + systemError("%s: Bad block marking %s.", pMtd->acName, strerror(iRet)); + } + + if (iBytesReadSrc > iBytesReadMtd) { + logMsg(LOG_ERR, + "\nSize mismatch. Source has %i Bytes but flash only %i bytes", + iBytesReadSrc, iBytesReadMtd); + goto ret; + } + + iOffs = MemCmp(pucBufferSrc, pucBufferMtd, iBytesReadSrc); + if (-1 != iOffs) { + logMsg(LOG_ERR, + "\nData mismatch @ 0x%08x\n", iBytesReadTotal + iOffs); + logMsg(LOG_ERR, "Source is"); + MemDump(pucBufferSrc, iOffs & ~0xf, MIN(iBytesReadSrc, 0x20)); + logMsg(LOG_ERR, "Flash is"); + MemDump(pucBufferMtd, iOffs & ~0xf, MIN(iBytesReadMtd, 0x20)); + goto ret; + } + + if (!pMtd->cIsJFFS2 && (iBytesReadSrc < iBytesReadMtd)) { + /* !TODO. JFFS2 is padded. + This is not yet checked. */ + int i; + + /* check for emptiness of block. */ + /* !TODO. This works only for NOR and NAND */ + for (i = iBytesReadSrc; i < iBytesReadMtd; i++) + if (FLASH_ERASED_BYTE != pucBufferMtd[i]) { + logMsg(LOG_ERR, + "Mtd is not empty @ 0x%llx\n", + iBytesReadTotal + i); + goto ret; + } + } /* if( iBytesReadSrc < iBytesReadMtd */ + iBytesReadTotal += iBytesReadSrc; + + cLastWasBad = 0; + } else { + logMsg(LOG_HARDWARE1, + "%s" WARNING "Skipping bad sector @ 0x%08x ", + (!cLastWasBad ? "\r" : ""), iOffsMtd); + cLastWasBad = 1; + } + + iOffsMtd += iBlockSize; + } while (iOffsMtd < pMtd->xInfo.size); + + if (pMtd->iFileSize != iBytesReadTotal) { + logMsg(LOG_ERR, "Filesize changed while updating: %s", pMtd->szImageFileName); + goto ret; + } + + PRINTF("\r Verifying: complete \n"); + + cRes = 1; + +ret: + CLOSE(iFdSrc); + + FREE(pucBufferMtd); + FREE(pucBufferSrc); + + return cRes; +} + +/*********************************************************************** + * !Function: MtdPartCheckCRC32 + * !Descr: calculates checksum of mtd partition up to filesize + ***********************************************************************/ +static void MtdPartCheckCRC32(mtdPartition_t * pMtd) +{ + unsigned char *pucBufferMtd; + loff_t iBytesReadTotal = 0; + loff_t iOffsMtd = 0; + uint32_t uiCRC32 = 0; + const size_t iBlockSize = pMtd->xInfo.erasesize; + int iThrottle = MtdPartGetThrottle(pMtd, pMtd->iFileSize); + + pucBufferMtd = (unsigned char *)malloc(iBlockSize); + if (NULL == pucBufferMtd) + systemError("malloc"); + + /* sync everything */ + MtdPartClose(pMtd); + MtdPartOpen(pMtd, 1); /* also rewinds read pointer */ + + do { + if (!MtdPartIsBadBlock(pMtd, iOffsMtd)) { + int iBytesReadMtd; + + PrintProgress(PERCENTAGE(iBytesReadTotal, pMtd->iFileSize), + iThrottle, " CRC32: "); + + iBytesReadMtd = pread(pMtd->iFd, pucBufferMtd, iBlockSize, iOffsMtd); + if (iBytesReadMtd < 0) + systemError("%s", pMtd->acName); + + if ((iBytesReadTotal + iBytesReadMtd) > pMtd->iFileSize) + iBytesReadMtd = (pMtd->iFileSize - iBytesReadTotal); + + uiCRC32 = crc32(uiCRC32, pucBufferMtd, iBytesReadMtd); + iBytesReadTotal += iBytesReadMtd; + } + iOffsMtd += iBlockSize; + } while (pMtd->iFileSize > iBytesReadTotal); + + PRINTF("\r CRC32: complete \n"); + + FREE(pucBufferMtd); + + MtdPartCompareCRC32(pMtd, uiCRC32); +} + +/*********************************************************************** + * !Function: MtdPartCopyFile + * !Descr: copies the image file to szDstFileName and calculates its + * check sum + ***********************************************************************/ +static void MtdPartCopyFile(mtdPartition_t * pMtd, const char *szDstFileName) +{ + unsigned char *pucBuffer = NULL; + int iFdDst = -1; + int iFdSrc = -1; + loff_t iBytesReadTotal = 0; + int iBytesRead; + uint32_t uiCRC32 = 0; + const size_t iBlockSize = pMtd->xInfo.erasesize; + + pucBuffer = (unsigned char *)malloc(iBlockSize); + if (NULL == pucBuffer) + systemError("malloc"); + + iFdSrc = open(pMtd->szImageFileName, O_RDONLY); + if (-1 == iFdSrc) + systemError("%s", pMtd->szImageFileName); + + iFdDst = open(szDstFileName, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + if (-1 == iFdDst) + systemError("%s", szDstFileName); + + do { + int iBytesWritten; + + iBytesRead = read(iFdSrc, pucBuffer, iBlockSize); + if (!iBytesRead) + break; + else if (-1 == iBytesRead) + systemError("%s", pMtd->szImageFileName); + + if (pMtd->cIsJFFS2) + MtdPartVerifyJFFS2Block(pMtd, pucBuffer, iBytesRead); + + uiCRC32 = crc32(uiCRC32, pucBuffer, iBytesRead); + + iBytesWritten = write(iFdDst, pucBuffer, iBytesRead); + if (iBytesWritten != iBytesRead) + systemError("%s", szDstFileName); + + iBytesReadTotal += iBytesRead; + } while (1); + + CLOSE(iFdDst); + CLOSE(iFdSrc); + + FREE(pucBuffer); + + if (pMtd->iFileSize != iBytesReadTotal) + error("Filesize changed while updating: %s", pMtd->szImageFileName); + + if (!pMtd->cChecksumSet) { + SET_CRC32(pMtd, uiCRC32); + pMtd->cChecksumCalculated = 1; + } else + MtdPartCompareCRC32(pMtd, uiCRC32); +} + +/*********************************************************************** + * !Function: MtdPartInitCleanMarker + * !Descr: initializes the clean marker + * !see [1] + ***********************************************************************/ +static void MtdPartInitCleanMarker(const mtdPartition_t * pMtd, + struct jffs2_unknown_node *pCleanMarker, + unsigned int *puiClMPos, unsigned int *puiClMLen) +{ + *puiClMPos = 0; + *puiClMLen = 8; + + if (pMtd->cIsNAND) { + struct nand_oobinfo xoobInfo; + + CLEAR(xoobInfo); + if (ioctl(pMtd->iFd, MEMGETOOBSEL, &xoobInfo)) + systemError("ioctl( MEMGETOOBSEL )"); + + /* check for autoplacement */ + if (MTD_NANDECC_AUTOPLACE == xoobInfo.useecc) { + /* get the position of the free bytes */ + if (!xoobInfo.oobfree[0][1]) + error("Autoplacement selected and no empty space in oob\n"); + + *puiClMPos = xoobInfo.oobfree[0][0]; + *puiClMLen = xoobInfo.oobfree[0][1]; + + if (*puiClMLen > 8) + *puiClMLen = 8; + else { + /* legacy mode, detect autoplacement ourselves [3] */ + switch (pMtd->xInfo.oobsize) { + case 8: + *puiClMPos = 6; + *puiClMLen = 2; + break; + case 16: + *puiClMPos = 8; + *puiClMLen = 8; + break; + case 64: + *puiClMPos = 16; + *puiClMLen = 8; + break; + default: + error("unsupported oobsize %i\n", pMtd->xInfo.oobsize); + break; + } /* switch */ + } /* if( *piClMPos ) */ + } /* if( NAND_AUTOPLACE */ + } else + MtdPartInitJFFS2Node(pCleanMarker, + JFFS2_NODETYPE_CLEANMARKER, + sizeof(struct jffs2_unknown_node)); + + logMsg(LOG_HARDWARE2, + " OOB has %i bytes, CleanMarker is from 0x%02x to 0x%02x", + pMtd->xInfo.oobsize, *puiClMPos, *puiClMPos + *puiClMLen); +} + +/*********************************************************************** + * !Function: MtdPartInitJFFS2Node + * !Descr: initializes a node and generates hdr checksum + ***********************************************************************/ +static void MtdPartInitJFFS2Node(struct jffs2_unknown_node *pNode, unsigned short uhNodeType, + size_t iLen) +{ + uint32_t uiStart = 0xffffffff; /* JFFS CRC32 starts from 0xfffffff, our crc32 from 0x0 */ + + CLEAR(*pNode); + + pNode->magic = cpu_to_je16(JFFS2_MAGIC_BITMASK); + pNode->nodetype = cpu_to_je16(uhNodeType); + + pNode->totlen = cpu_to_je32(iLen); + /* don't CRC32 the hdr_crc itself */ + pNode->hdr_crc = + cpu_to_je32(crc32(uiStart, pNode, sizeof(struct jffs2_unknown_node) - 4) ^ uiStart); +} + +/*********************************************************************** + * !Function: MtdPartCompareCRC32 + * !Descr: checks whether the CRC32 is same as previously calculated. + ***********************************************************************/ +static void MtdPartCompareCRC32(mtdPartition_t * pMtd, uint32_t uiCRC32) +{ + if (uiCRC32 != pMtd->uiCRC32) + error("CRC32 mismatch on %s.\n Expected 0x%08x, have 0x%08x\n", + pMtd->szImageFileName, pMtd->uiCRC32, uiCRC32); +} + +/*********************************************************************** + * !Function: MtdPartRemountAllReadOnly + * !Descr: remounts all uses of the partition as read-only + ***********************************************************************/ +static void MtdPartRemountAllReadOnly(mtdPartition_t * pMtd) +{ + static const char *szMount = "/proc/mounts"; + char acMtdBlock[20]; + FILE *fhMount = NULL; + const char *szRootDev = GetRootDevice(); + + if (pMtd->cAlreadyRemounted) + /* nothing to do */ + return; + + fhMount = setmntent(szMount, "r"); + if (NULL == fhMount) + systemError(szMount); + + sprintf(acMtdBlock, "%s%u", szMtdBlockPrefix, pMtd->uiPartition); + + /* scan all mount entries */ + do { + char bIsRootDev = 0; + char bRemount = 0; + + struct mntent *pxEnt = getmntent(fhMount); + if (NULL == pxEnt) + break; + + bIsRootDev = (!strcmp(pxEnt->mnt_fsname, "/dev/root") && + !strcmp(szRootDev, acMtdBlock)); + if (!bIsRootDev && !strcmp(pxEnt->mnt_fsname, acMtdBlock)) { + PRINTF(WARNING "Umounting %s\n", pxEnt->mnt_dir); + if (umount(pxEnt->mnt_dir) || bIsRootDev) { + /* rootdev can't be unmounted */ + /* it's for our mtd partition, remount it */ + PRINTF(WARNING "Failed, trying to remount read-only\n"); + bRemount = 1; + } + } + if (bIsRootDev || bRemount) { + PRINTF(WARNING "Remounting %s\n", pxEnt->mnt_dir); + if (mount(pxEnt->mnt_fsname, pxEnt->mnt_dir, + NULL, MS_REMOUNT | MS_RDONLY, NULL)) { + if (EBUSY == errno) + error("Partition in use, can't update it\n"); + else + systemError("%s", pxEnt->mnt_fsname); + } + } + } while (1); + + endmntent(fhMount); + + pMtd->cAlreadyRemounted = 1; +} + +/*********************************************************************** + * !Function: MtdPartDeterminePartType + * !Descr: Determine partition type (rootfs, uboot, etc.) based on + * partition name in /proc/mtd + ***********************************************************************/ +static void MtdPartDeterminePartType(mtdPartition_t * pMtd) +{ + static const char *szMtd = "/proc/mtd"; + FILE *fhMtd = NULL; + unsigned int uiPart; + char acBuffer[200]; + int iPart; + loff_t uiSize; + loff_t uiEraseSize; + char acName[200]; + static const struct { + PartType_e eType; + FileType_e eFileType; + const char *szName; + } axTypes[] = { + {PTUBoot, FTUBoot, "\"U-Boot"}, + {PTKernel, FTKernel, "\"Kernel"}, + {PTEnvironment, FTNVRAM, "\"NVRAM"}, + {PTFPGA, FTFPGA, "\"FPGA"}, + {PTBootstream, FTBootstream, "\"Bstrm-U-Boot"}, + }; + int i; + + /* open /proc/mtd */ + fhMtd = fopen(szMtd, "r"); + if (NULL == fhMtd) + systemError(szMtd); + + /* skip table header (check return value to avoid compiler warning) */ + if (fgets(acBuffer, sizeof(acBuffer), fhMtd)); + + /* seek partition description */ + for (iPart = 0; iPart <= pMtd->uiPartition; iPart++) + if (NULL == fgets(acBuffer, sizeof(acBuffer) - 1, fhMtd)) + systemError(szMtd); + + /* break it into parts */ + acBuffer[sizeof(acBuffer) - 1] = 0; + if (sscanf(acBuffer, "mtd%u: %llx %llx %199s", + &uiPart, &uiSize, &uiEraseSize, acName) != 4) + error("Wrong /proc/mtd line: %s", acBuffer); + if (uiPart != pMtd->uiPartition) + error("Wrong partition: %s", acBuffer); + + /* determine partition type */ + pMtd->ePartType = PTUnknown; + pMtd->eFileTypeNeeded = FTUnknown; + for (i = 0; i < ARRAY_SIZE(axTypes); i++) { + if (!strncmp(axTypes[i].szName, acName, strlen(axTypes[i].szName))) { + pMtd->ePartType = axTypes[i].eType; + pMtd->eFileTypeNeeded = axTypes[i].eFileType; + break; + } + } + + if (fclose(fhMtd) < 0) + systemError(szMtd); +} + +/*********************************************************************** + * !Function: MtdPartDetermineAndCheckFileType + * !Descr: Determine file type (rootfs, uboot, etc.) and checks whether it + * is ok for the partition. Done by looking at filename + ***********************************************************************/ +static void MtdPartDetermineAndCheckFileType(mtdPartition_t * pMtd) +{ + FileType_e eType = 0; + int iMatch = 0; + regex_t regexp; + + CLEAR(regexp); + + /* !TODO: Verify File Type, JFFS2, uimage, u-boot by looking into it */ + + while (eType < ARRAY_SIZE(axFileType)) { + /* does axFileType[ eType ] matches pMtd->szImageFileName? */ + int iError = regcomp(®exp, + axFileType[eType].szExp, + REG_NOSUB); + if (iError) { + char acError[200]; + regerror(iError, ®exp, acError, sizeof(acError) - 1); + error("%s", acError); + } + + iMatch = !regexec(®exp, pMtd->szImageFileName, 0, NULL, 0); + regfree(®exp); + + if (iMatch) { + pMtd->eFileType = eType; + + logMsg(LOG_HARDWARE1, + " Detected Image Type for %s: %s", + pMtd->szImageFileName, axFileType[eType].szName); + + /* check whether it matches the partition */ + if ((pMtd->eFileTypeNeeded != pMtd->eFileType) && + (pMtd->eFileTypeNeeded != FTUnknown)) + error("File %s (Type %s) doesn't match partition type %s", + pMtd->szImageFileName, + axFileType[pMtd->eFileType].szName, + aszPartType[pMtd->ePartType]); + + if (FTJFFS2 == pMtd->eFileType) + pMtd->cIsJFFS2 = 1; + + break; + } + eType++; + } +} + +/*********************************************************************** + * !Function: MtdPartVerifyFile + * !Descr: verifies the file (checksum, JFFS2) + ***********************************************************************/ +static void MtdPartVerifyFile(mtdPartition_t * pMtd) +{ + unsigned char *pucBuffer = NULL; + uint32_t uiCRC32 = 0; + int iFd = -1; + int iBytesRead; + const size_t iBlockSize = pMtd->xInfo.erasesize; + + pucBuffer = (unsigned char *)malloc(iBlockSize); + if (NULL == pucBuffer) + systemError("malloc"); + + iFd = open(pMtd->szImageFileName, O_RDONLY); + if (-1 == iFd) + systemError("%s", pMtd->szImageFileName); + + do { + iBytesRead = read(iFd, pucBuffer, iBlockSize); + if (!iBytesRead) + break; + else if (-1 == iBytesRead) + systemError("%s", pMtd->szImageFileName); + + if (pMtd->cIsJFFS2) + MtdPartVerifyJFFS2Block(pMtd, pucBuffer, iBytesRead); + + uiCRC32 = crc32(uiCRC32, pucBuffer, iBytesRead); + } while (1); + + CLOSE(iFd); + + FREE(pucBuffer); + + if (!pMtd->cChecksumSet) { + SET_CRC32(pMtd, uiCRC32); + pMtd->cChecksumCalculated = 1; + } else + MtdPartCompareCRC32(pMtd, uiCRC32); +} + +/*********************************************************************** + * !Function: MtdPartVerifyJFFS2Block + * !Descr: verifies whether the JFFS2 block is correct. On NOR clean markers + * need to be on the correct position, on NAND there mustn't be. + ***********************************************************************/ +static void MtdPartVerifyJFFS2Block(mtdPartition_t * pMtd, unsigned char *pucData, size_t iSize) +{ + char cCleanMarkerPresent = 0; + int iOffs; + /* only magic and node, length field of xCleanMarker may vary */ + static const size_t NODE_SIZE = 4; + + if (pMtd->cAlreadyPrintedVerifyWarning) + /* do it only once */ + return; + + if (iSize >= 4) + cCleanMarkerPresent = (MemCmp(pucData, &pMtd->xCleanMarker, NODE_SIZE) == -1); + + if (pMtd->cIsNAND) { + const jint16_t MAGIC = cpu_to_je16(JFFS2_MAGIC_BITMASK); + + if (cCleanMarkerPresent) { + logMsg(LOG_ERR, + WARNING + "CleanMarkers present in image for NAND. JFFS2 will complain but function."); + pMtd->cAlreadyPrintedVerifyWarning = 1; + } + + /* MAGIC is < sizeof( xCleanMarker ) */ + if (MemCmp(pucData, &MAGIC, sizeof(MAGIC)) != -1) + error("No JFFS2 Header at erase block begin"); + } else { + /* NOR etc. */ + if (!cCleanMarkerPresent) + error + ("No CleanMarkers present in image for NOR. JFFS2 won't like that. Possibly wrong erase block size of jffs2 or NAND image"); + + /* there shouldn't be any other clean marker in the block. + Check 2^n offsets. */ + for (iOffs = 16; iOffs < iSize - NODE_SIZE; iOffs *= 2) { + if (MemCmp(pucData + iOffs, &pMtd->xCleanMarker, NODE_SIZE) == -1) + error + ("Clean Markers present in the block at offset 0x%08x. Possibly wrong erase block size of jffs2", + iOffs); + } + } +} + +/*********************************************************************** + * !Function: MtdPartGetThrottle + ***********************************************************************/ +static int MtdPartGetThrottle(const mtdPartition_t * pMtd, uint64_t ullSize) +{ + /* Eclipse has a nicer output with an update every second. Therefore it + * is not throttled. */ + return 1; +} + +/*********************************************************************** + * !Function: PrintProgress + * !Descr: Prints message only when progress has changed + ***********************************************************************/ +static void PrintProgress(int iPercentage, int iThrottle, const char *szFmt, ...) +{ + static int iLastPercentage = -1; + int iThrottled = iPercentage / iThrottle; + + if (cSilent) + /* nothing to print */ + return; + + if (iThrottled != iLastPercentage) { + va_list args; + + iLastPercentage = iThrottled; + + va_start(args, szFmt); + vprintf(szFmt, args); + printf("% 3i%% \r", iPercentage); + va_end(args); + + if (cProgressInNewLine) + PRINTF("\n"); + fflush(stdout); + } +} + +/*********************************************************************** + * !Function: VerifyTmpDir + * !Descr: checks whether the temporary directory can be used + ***********************************************************************/ +static void VerifyTmpDir(void) +{ + struct statfs xStat; + + CLEAR(xStat); + + if (statfs(szTmpDir, &xStat)) + systemError("statfs"); + + switch (xStat.f_type) { + case JFFS2_SUPER_MAGIC: + /* flash image temporary on flash fs??? */ + error("Makes no sense to store flash image temporarily on JFFS2"); + break; /* not reached */ + case NFS_SUPER_MAGIC: + error("Makes no sense to store flash image temporarily on NFS"); + break; /* not reached */ + default: + /* Accept all other ones. If they are read-only, it is detected before + * erasing flash */ + break; + } +} + +/*********************************************************************** + * !Function: CalcCRC32OfFile + * !Descr: calculates the CRC32 of the file. + ***********************************************************************/ +static uint32_t CalcCRC32OfFile(const char *szFileName) +{ + char acBuffer[IO_BLOCK_SIZE]; + int iFd = -1; + uint32_t uiCRC32 = 0; + + iFd = open(szFileName, O_RDONLY); + if (-1 == iFd) + systemError("%s", szFileName); + + do { + int iBytesRead = read(iFd, acBuffer, IO_BLOCK_SIZE); + + if (!iBytesRead) + break; + else if (iBytesRead < 0) + systemError("%s", szFileName); + + uiCRC32 = crc32(uiCRC32, acBuffer, iBytesRead); + } while (1); + + CLOSE(iFd); + + return uiCRC32; +} + +/*********************************************************************** + * !Function: GetRootDevice + * !Descr: determines the rootdevice from kernel command line root= + ***********************************************************************/ +static const char *GetRootDevice(void) +{ + int iFd = -1; + int iRead; + /* arm command line is at max 1024 Bytes */ + char szCmdLine[1024]; + static char szRootDev[32] = ""; + const char *szRootStart = NULL; + + if (*szRootDev) + return szRootDev; + + iFd = open("/proc/cmdline", O_RDONLY); + if (-1 == iFd) + systemError("/proc/cmdline"); + + iRead = read(iFd, szCmdLine, sizeof(szCmdLine) - 2); + if (-1 == iRead) + systemError("read"); + szCmdLine[iRead + 1] = 0; + + CLOSE(iFd); + + szRootStart = strstr(szCmdLine, "root="); + if (NULL != szRootStart) { + const char *szDev = szRootStart + 5; /* strlen( root=) */ + const char *szDevEnd = szDev; + int iLen; + + szDevEnd = strchr(szDev, ' '); + iLen = ((NULL == szDevEnd) ? strlen(szDev) : (szDevEnd - szDev)); + + strncpy(szRootDev, szDev, MIN(sizeof(szRootDev), iLen)); + } + + return szRootDev; +}