diff options
Diffstat (limited to '')
-rw-r--r-- | mtdutils/Android.mk | 69 | ||||
-rw-r--r-- | mtdutils/bml_over_mtd.c | 810 | ||||
-rw-r--r-- | mtdutils/mounts.c | 223 | ||||
-rw-r--r-- | mtdutils/mounts.h | 38 | ||||
-rw-r--r-- | mtdutils/mtdutils.c | 812 | ||||
-rw-r--r-- | mtdutils/mtdutils.h | 69 | ||||
-rw-r--r-- | mtdutils/rk3xhack.c | 60 | ||||
-rw-r--r-- | mtdutils/rk3xhack.h | 37 |
8 files changed, 2118 insertions, 0 deletions
diff --git a/mtdutils/Android.mk b/mtdutils/Android.mk new file mode 100644 index 000000000..87ac08129 --- /dev/null +++ b/mtdutils/Android.mk @@ -0,0 +1,69 @@ +ifneq ($(TARGET_SIMULATOR),true) + +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + mtdutils.c \ + mounts.c + +ifneq ($(filter rk30xx rk3188,$(TARGET_BOARD_PLATFORM)),) +LOCAL_SRC_FILES += rk3xhack.c +LOCAL_CFLAGS += -DRK3X +endif + +ifeq ($(TARGET_MTD_BY_NAME),true) +LOCAL_CFLAGS += -DBYNAME +endif + +LOCAL_MODULE := libmtdutils +LOCAL_STATIC_LIBRARIES := libcutils libc +LOCAL_CLANG := true + +include $(BUILD_STATIC_LIBRARY) + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + mtdutils.c \ + mounts.c + +ifneq ($(filter rk30xx rk3188,$(TARGET_BOARD_PLATFORM)),) +LOCAL_SRC_FILES += rk3xhack.c +LOCAL_CFLAGS += -DRK3X +endif + +ifeq ($(TARGET_MTD_BY_NAME),true) +LOCAL_CFLAGS += -DBYNAME +endif + +LOCAL_MODULE := libmtdutils +LOCAL_SHARED_LIBRARIES := libcutils libc +LOCAL_CLANG := true + +include $(BUILD_SHARED_LIBRARY) + +ifeq ($(BOARD_USES_BML_OVER_MTD),true) +include $(CLEAR_VARS) +LOCAL_SRC_FILES := bml_over_mtd.c +LOCAL_C_INCLUDES += $(commands_recovery_local_path)/mtdutils +LOCAL_MODULE := libbml_over_mtd +LOCAL_MODULE_TAGS := eng +LOCAL_CFLAGS += -Dmain=bml_over_mtd_main +include $(BUILD_STATIC_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_SRC_FILES := bml_over_mtd.c +LOCAL_MODULE := bml_over_mtd +LOCAL_MODULE_TAGS := eng +LOCAL_MODULE_CLASS := UTILITY_EXECUTABLES +LOCAL_MODULE_PATH := $(PRODUCT_OUT)/utilities +LOCAL_UNSTRIPPED_PATH := $(PRODUCT_OUT)/symbols/utilities +LOCAL_MODULE_STEM := bml_over_mtd +LOCAL_C_INCLUDES += $(commands_recovery_local_path)/mtdutils +LOCAL_SHARED_LIBRARIES := libmtdutils libcutils liblog libc +include $(BUILD_EXECUTABLE) +endif + +endif # !TARGET_SIMULATOR + diff --git a/mtdutils/bml_over_mtd.c b/mtdutils/bml_over_mtd.c new file mode 100644 index 000000000..12ca10915 --- /dev/null +++ b/mtdutils/bml_over_mtd.c @@ -0,0 +1,810 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/ioctl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <limits.h> + +#include "cutils/log.h" + +#include <mtd/mtd-user.h> + +#include "mtdutils.h" + +#ifdef RK3X + #include "rk3xhack.h" +#endif + +typedef struct BmlOverMtdReadContext { + const MtdPartition *partition; + char *buffer; + size_t consumed; + int fd; +} BmlOverMtdReadContext; + +typedef struct BmlOverMtdWriteContext { + const MtdPartition *partition; + char *buffer; + size_t stored; + int fd; + + off_t* bad_block_offsets; + int bad_block_alloc; + int bad_block_count; +} BmlOverMtdWriteContext; + + +static BmlOverMtdReadContext *bml_over_mtd_read_partition(const MtdPartition *partition) +{ + BmlOverMtdReadContext *ctx = (BmlOverMtdReadContext*) malloc(sizeof(BmlOverMtdReadContext)); + if (ctx == NULL) return NULL; + + ctx->buffer = malloc(partition->erase_size); + if (ctx->buffer == NULL) { + free(ctx); + return NULL; + } + + char mtddevname[32]; + sprintf(mtddevname, "/dev/mtd/mtd%d", partition->device_index); + ctx->fd = open(mtddevname, O_RDONLY); + if (ctx->fd < 0) { + free(ctx); + free(ctx->buffer); + return NULL; + } + + ctx->partition = partition; + ctx->consumed = partition->erase_size; + return ctx; +} + +static void bml_over_mtd_read_close(BmlOverMtdReadContext *ctx) +{ + close(ctx->fd); + free(ctx->buffer); + free(ctx); +} + +static BmlOverMtdWriteContext *bml_over_mtd_write_partition(const MtdPartition *partition) +{ + BmlOverMtdWriteContext *ctx = (BmlOverMtdWriteContext*) malloc(sizeof(BmlOverMtdWriteContext)); + if (ctx == NULL) return NULL; + + ctx->bad_block_offsets = NULL; + ctx->bad_block_alloc = 0; + ctx->bad_block_count = 0; + + ctx->buffer = malloc(partition->erase_size); + if (ctx->buffer == NULL) { + free(ctx); + return NULL; + } + + char mtddevname[32]; + sprintf(mtddevname, "/dev/mtd/mtd%d", partition->device_index); + ctx->fd = open(mtddevname, O_RDWR); + if (ctx->fd < 0) { + free(ctx->buffer); + free(ctx); + return NULL; + } + + ctx->partition = partition; + ctx->stored = 0; + return ctx; +} + +static int bml_over_mtd_write_close(BmlOverMtdWriteContext *ctx) +{ + int r = 0; + if (close(ctx->fd)) r = -1; + free(ctx->bad_block_offsets); + free(ctx->buffer); + free(ctx); + return r; +} + + +#ifdef LOG_TAG +#undef LOG_TAG +#endif + +#define LOG_TAG "bml_over_mtd" + +#define BLOCK_SIZE 2048 +#define SPARE_SIZE (BLOCK_SIZE >> 5) + +#define EXIT_CODE_BAD_BLOCKS 15 + +static int die(const char *msg, ...) { + int err = errno; + va_list args; + va_start(args, msg); + char buf[1024]; + vsnprintf(buf, sizeof(buf), msg, args); + va_end(args); + + if (err != 0) { + strlcat(buf, ": ", sizeof(buf)); + strlcat(buf, strerror(err), sizeof(buf)); + } + + fprintf(stderr, "%s\n", buf); + return 1; +} + +static unsigned short* CreateEmptyBlockMapping(const MtdPartition* pSrcPart) +{ + size_t srcTotal, srcErase, srcWrite; + if (mtd_partition_info(pSrcPart, &srcTotal, &srcErase, &srcWrite) != 0) + { + fprintf(stderr, "Failed to access partition.\n"); + return NULL; + } + + int numSrcBlocks = srcTotal/srcErase; + + unsigned short* pMapping = malloc(numSrcBlocks * sizeof(unsigned short)); + if (pMapping == NULL) + { + fprintf(stderr, "Failed to allocate block mapping memory.\n"); + return NULL; + } + memset(pMapping, 0xFF, numSrcBlocks * sizeof(unsigned short)); + return pMapping; +} + +static const unsigned short* CreateBlockMapping(const MtdPartition* pSrcPart, int srcPartStartBlock, + const MtdPartition *pReservoirPart, int reservoirPartStartBlock) +{ + size_t srcTotal, srcErase, srcWrite; + if (mtd_partition_info(pSrcPart, &srcTotal, &srcErase, &srcWrite) != 0) + { + fprintf(stderr, "Failed to access partition.\n"); + return NULL; + } + + int numSrcBlocks = srcTotal/srcErase; + + unsigned short* pMapping = malloc(numSrcBlocks * sizeof(unsigned short)); + if (pMapping == NULL) + { + fprintf(stderr, "Failed to allocate block mapping memory.\n"); + return NULL; + } + memset(pMapping, 0xFF, numSrcBlocks * sizeof(unsigned short)); + + size_t total, erase, write; + if (mtd_partition_info(pReservoirPart, &total, &erase, &write) != 0) + { + fprintf(stderr, "Failed to access reservoir partition.\n"); + free(pMapping); + return NULL; + } + + if (erase != srcErase || write != srcWrite) + { + fprintf(stderr, "Source partition and reservoir partition differ in size properties.\n"); + free(pMapping); + return NULL; + } + + printf("Partition info: Total %d, Erase %d, write %d\n", total, erase, write); + + BmlOverMtdReadContext *readctx = bml_over_mtd_read_partition(pReservoirPart); + if (readctx == NULL) + { + fprintf(stderr, "Failed to open reservoir partition for reading.\n"); + free(pMapping); + return NULL; + } + + if (total < erase || total > INT_MAX) + { + fprintf(stderr, "Unsuitable reservoir partition properties.\n"); + free(pMapping); + bml_over_mtd_read_close(readctx); + return NULL; + } + + int foundMappingTable = 0; + + int currOffset = total; //Offset *behind* the last byte + while (currOffset > 0) + { + currOffset -= erase; + loff_t pos = lseek64(readctx->fd, currOffset, SEEK_SET); + int mgbb = ioctl(readctx->fd, MEMGETBADBLOCK, &pos); + if (mgbb != 0) + { + printf("Bad block %d in reservoir area, skipping.\n", currOffset/erase); + continue; + } + ssize_t readBytes = read(readctx->fd, readctx->buffer, erase); + if (readBytes != (ssize_t)erase) + { + fprintf(stderr, "Failed to read good block in reservoir area (%s).\n", + strerror(errno)); + free(pMapping); + bml_over_mtd_read_close(readctx); + return NULL; + } + if (readBytes >= 0x2000) + { + char* buf = readctx->buffer; + if (buf[0]=='U' && buf[1]=='P' && buf[2]=='C' && buf[3]=='H') + { + printf ("Found mapping block mark at 0x%x (block %d).\n", currOffset, currOffset/erase); + + unsigned short* mappings = (unsigned short*) &buf[0x1000]; + if (mappings[0]==0 && mappings[1]==0xffff) + { + printf("Found start of mapping table.\n"); + foundMappingTable = 1; + //Skip first entry (dummy) + unsigned short* mappingEntry = mappings + 2; + while (mappingEntry - mappings < 100 + && mappingEntry[0] != 0xffff) + { + unsigned short rawSrcBlk = mappingEntry[0]; + unsigned short rawDstBlk = mappingEntry[1]; + + printf("Found raw block mapping %d -> %d\n", rawSrcBlk, + rawDstBlk); + + unsigned int srcAbsoluteStartAddress = srcPartStartBlock * erase; + unsigned int resAbsoluteStartAddress = reservoirPartStartBlock * erase; + + int reservoirLastBlock = reservoirPartStartBlock + numSrcBlocks - 1; + if (rawDstBlk < reservoirPartStartBlock + || rawDstBlk*erase >= resAbsoluteStartAddress+currOffset) + { + fprintf(stderr, "Mapped block not within reasonable reservoir area.\n"); + foundMappingTable = 0; + break; + } + + int srcLastBlock = srcPartStartBlock + numSrcBlocks - 1; + if (rawSrcBlk >= srcPartStartBlock && rawSrcBlk <= srcLastBlock) + { + + unsigned short relSrcBlk = rawSrcBlk - srcPartStartBlock; + unsigned short relDstBlk = rawDstBlk - reservoirPartStartBlock; + printf("Partition relative block mapping %d -> %d\n",relSrcBlk, relDstBlk); + + printf("Absolute mapped start addresses 0x%x -> 0x%x\n", + srcAbsoluteStartAddress+relSrcBlk*erase, + resAbsoluteStartAddress+relDstBlk*erase); + printf("Partition relative mapped start addresses 0x%x -> 0x%x\n", + relSrcBlk*erase, relDstBlk*erase); + + //Set mapping entry. For duplicate entries, later entries replace former ones. + //*Assumption*: Bad blocks in reservoir area will not be mapped themselves in + //the mapping table. User partition blocks will not be mapped to bad blocks + //(only) in the reservoir area. This has to be confirmed on a wider range of + //devices. + pMapping[relSrcBlk] = relDstBlk; + + } + mappingEntry+=2; + } + break; //We found the mapping table, no need to search further + } + + + } + } + + } + bml_over_mtd_read_close(readctx); + + if (foundMappingTable == 0) + { + fprintf(stderr, "Cannot find mapping table in reservoir partition.\n"); + free(pMapping); + return NULL; + } + + //Consistency and validity check + int mappingValid = 1; + readctx = bml_over_mtd_read_partition(pSrcPart); + if (readctx == NULL) + { + fprintf(stderr, "Cannot open source partition for reading.\n"); + free(pMapping); + return NULL; + } + int currBlock = 0; + for (;currBlock < numSrcBlocks; ++currBlock) + { + loff_t pos = lseek64(readctx->fd, currBlock*erase, SEEK_SET); + int mgbb = ioctl(readctx->fd, MEMGETBADBLOCK, &pos); + if (mgbb == 0) + { + if (pMapping[currBlock]!=0xffff) + { + fprintf(stderr, "Consistency error: Good block has mapping entry %d -> %d\n", currBlock, pMapping[currBlock]); + mappingValid = 0; + } + } else + { + //Bad block! + if (pMapping[currBlock]==0xffff) + { + fprintf(stderr, "Consistency error: Bad block has no mapping entry \n"); + mappingValid = 0; + } else + { + BmlOverMtdReadContext* reservoirReadCtx = bml_over_mtd_read_partition(pReservoirPart); + if (reservoirReadCtx == 0) + { + fprintf(stderr, "Reservoir partition cannot be opened for reading in consistency check.\n"); + mappingValid = 0; + } else + { + pos = lseek64(reservoirReadCtx->fd, pMapping[currBlock]*erase, SEEK_SET); + mgbb = ioctl(reservoirReadCtx->fd, MEMGETBADBLOCK, &pos); + if (mgbb == 0) + { + printf("Bad block has properly mapped reservoir block %d -> %d\n",currBlock, pMapping[currBlock]); + } + else + { + fprintf(stderr, "Consistency error: Mapped block is bad, too. (%d -> %d)\n",currBlock, pMapping[currBlock]); + mappingValid = 0; + } + + } + bml_over_mtd_read_close(reservoirReadCtx); + } + + } + + } + bml_over_mtd_read_close(readctx); + + + if (!mappingValid) + { + free(pMapping); + return NULL; + } + + return pMapping; +} + +static void ReleaseBlockMapping(const unsigned short* blockMapping) +{ + free((void*)blockMapping); +} + +static int dump_bml_partition(const MtdPartition* pSrcPart, const MtdPartition* pReservoirPart, + const unsigned short* blockMapping, const char* filename) +{ + int fd = open(filename, O_WRONLY|O_CREAT|O_TRUNC, 0666); + if (fd < 0) + { + fprintf(stderr, "error opening %s", filename); + return -1; + } + BmlOverMtdReadContext* pSrcRead = bml_over_mtd_read_partition(pSrcPart); + if (pSrcRead == NULL) + { + close(fd); + fprintf(stderr, "dump_bml_partition: Error opening src part for reading.\n"); + return -1; + } + + BmlOverMtdReadContext* pResRead = bml_over_mtd_read_partition(pReservoirPart); + if (pResRead == NULL) + { + close(fd); + bml_over_mtd_read_close(pSrcRead); + fprintf(stderr, "dump_bml_partition: Error opening reservoir part for reading.\n"); + return -1; + } + + + int numBlocks = pSrcPart->size / pSrcPart->erase_size; + int currblock = 0; + for (;currblock < numBlocks; ++currblock) + { + int srcFd = -1; + if (blockMapping[currblock] == 0xffff) + { + //Good block, use src partition + srcFd = pSrcRead->fd; + if (lseek64(pSrcRead->fd, currblock*pSrcPart->erase_size, SEEK_SET)==-1) + { + close(fd); + bml_over_mtd_read_close(pSrcRead); + bml_over_mtd_read_close(pResRead); + fprintf(stderr, "dump_bml_partition: lseek in src partition failed\n"); + return -1; + } + } else + { + //Bad block, use mapped block in reservoir partition + srcFd = pResRead->fd; + if (lseek64(pResRead->fd, blockMapping[currblock]*pSrcPart->erase_size, SEEK_SET)==-1) + { + close(fd); + bml_over_mtd_read_close(pSrcRead); + bml_over_mtd_read_close(pResRead); + fprintf(stderr, "dump_bml_partition: lseek in reservoir partition failed\n"); + return -1; + } + } + size_t blockBytesRead = 0; + while (blockBytesRead < pSrcPart->erase_size) + { + ssize_t len = read(srcFd, pSrcRead->buffer + blockBytesRead, + pSrcPart->erase_size - blockBytesRead); + if (len <= 0) + { + close(fd); + bml_over_mtd_read_close(pSrcRead); + bml_over_mtd_read_close(pResRead); + fprintf(stderr, "dump_bml_partition: reading partition failed\n"); + return -1; + } + blockBytesRead += len; + } + + size_t blockBytesWritten = 0; + while (blockBytesWritten < pSrcPart->erase_size) + { + ssize_t len = write(fd, pSrcRead->buffer + blockBytesWritten, + pSrcPart->erase_size - blockBytesWritten); + if (len <= 0) + { + close(fd); + bml_over_mtd_read_close(pSrcRead); + bml_over_mtd_read_close(pResRead); + fprintf(stderr, "dump_bml_partition: writing partition dump file failed\n"); + return -1; + } + blockBytesWritten += len; + } + + } + + bml_over_mtd_read_close(pSrcRead); + bml_over_mtd_read_close(pResRead); + + if (close(fd)) { + unlink(filename); + printf("error closing %s", filename); + return -1; + } + + return 0; +} + +static ssize_t bml_over_mtd_write_block(int fd, ssize_t erase_size, char* data) +{ + off_t pos = lseek(fd, 0, SEEK_CUR); + if (pos == (off_t) -1) return -1; + + ssize_t size = erase_size; + loff_t bpos = pos; + int ret = ioctl(fd, MEMGETBADBLOCK, &bpos); + if (ret != 0 && !(ret == -1 && errno == EOPNOTSUPP)) { + fprintf(stderr, + "Mapping failure: Trying to write bad block at 0x%08lx (ret %d errno %d)\n", + pos, ret, errno); + return -1; + } + + struct erase_info_user erase_info; + erase_info.start = pos; + erase_info.length = size; + int retry; + for (retry = 0; retry < 2; ++retry) { +#ifdef RK3X + if (rk30_zero_out(fd, pos, size) < 0) { + fprintf(stderr, "mtd: erase failure at 0x%08lx (%s)\n", + pos, strerror(errno)); + continue; + } +#else + if (ioctl(fd, MEMERASE, &erase_info) < 0) { + fprintf(stderr, "mtd: erase failure at 0x%08lx (%s)\n", + pos, strerror(errno)); + continue; + } +#endif + if (lseek(fd, pos, SEEK_SET) != pos || + write(fd, data, size) != size) { + fprintf(stderr, "mtd: write error at 0x%08lx (%s)\n", + pos, strerror(errno)); + } + + char verify[size]; + if (lseek(fd, pos, SEEK_SET) != pos || + read(fd, verify, size) != size) { + fprintf(stderr, "mtd: re-read error at 0x%08lx (%s)\n", + pos, strerror(errno)); + continue; + } + if (memcmp(data, verify, size) != 0) { + fprintf(stderr, "mtd: verification error at 0x%08lx (%s)\n", + pos, strerror(errno)); + continue; + } + + if (retry > 0) { + fprintf(stderr, "mtd: wrote block after %d retries\n", retry); + } + fprintf(stderr, "mtd: successfully wrote block at %llx\n", pos); + return size; // Success! + } + + + fprintf(stderr, "mtd: Block at %llx could not be properly written.\n", pos); + // Ran out of space on the device + errno = ENOSPC; + return -1; +} + +static int flash_bml_partition(const MtdPartition* pSrcPart, const MtdPartition* pReservoirPart, + const unsigned short* blockMapping, const char* filename) +{ + int fd = open(filename, O_RDONLY); + if (fd < 0) + { + fprintf(stderr, "error opening %s", filename); + return -1; + } + BmlOverMtdWriteContext* pSrcWrite = bml_over_mtd_write_partition(pSrcPart); + if (pSrcWrite == NULL) + { + close(fd); + fprintf(stderr, "flash_bml_partition: Error opening src part for writing.\n"); + return -1; + } + +#ifdef DUMMY_WRITING + close(pSrcWrite->fd); + pSrcWrite->fd = open("/sdcard/srcPartWriteDummy.bin", O_WRONLY|O_CREAT|O_TRUNC, 0666); +#endif + + BmlOverMtdWriteContext* pResWrite = bml_over_mtd_write_partition(pReservoirPart); + if (pResWrite == NULL) + { + close(fd); + bml_over_mtd_write_close(pSrcWrite); + fprintf(stderr, "flash_bml_partition: Error opening reservoir part for writing.\n"); + return -1; + } +#ifdef DUMMY_WRITING + close(pResWrite->fd); + pResWrite->fd = open("/sdcard/resPartWriteDummy.bin", O_WRONLY|O_CREAT|O_TRUNC, 0666); +#endif + + struct stat fileStat; + if (fstat(fd, &fileStat) != 0) + { + close(fd); + bml_over_mtd_write_close(pSrcWrite); + bml_over_mtd_write_close(pResWrite); + fprintf(stderr, "flash_bml_partition: Failed to stat source file.\n"); + return -1; + + } + if (fileStat.st_size > pSrcPart->size) + { + close(fd); + bml_over_mtd_write_close(pSrcWrite); + bml_over_mtd_write_close(pResWrite); + fprintf(stderr, "flash_bml_partition: Source file too large for target partition.\n"); + return -1; + } + + int numBlocks = (fileStat.st_size + pSrcPart->erase_size - 1) / pSrcPart->erase_size; + int currblock; + for (currblock = 0 ;currblock < numBlocks; ++currblock) + { + memset(pSrcWrite->buffer, 0xFF, pSrcPart->erase_size); + size_t blockBytesRead = 0; + while (blockBytesRead < pSrcPart->erase_size) + { + ssize_t len = read(fd, pSrcWrite->buffer + blockBytesRead, + pSrcPart->erase_size - blockBytesRead); + if (len < 0) + { + close(fd); + bml_over_mtd_write_close(pSrcWrite); + bml_over_mtd_write_close(pResWrite); + fprintf(stderr, "flash_bml_partition: read source file failed\n"); + return -1; + } + if (len == 0) + { + //End of file + break; + } + + blockBytesRead += len; + } + + + + int srcFd = -1; + if (blockMapping[currblock] == 0xffff) + { + //Good block, use src partition + srcFd = pSrcWrite->fd; + if (lseek64(pSrcWrite->fd, currblock*pSrcPart->erase_size, SEEK_SET)==-1) + { + close(fd); + bml_over_mtd_write_close(pSrcWrite); + bml_over_mtd_write_close(pResWrite); + fprintf(stderr, "flash_bml_partition: lseek in src partition failed\n"); + return -1; + } + } else + { + //Bad block, use mapped block in reservoir partition + srcFd = pResWrite->fd; + if (lseek64(pResWrite->fd, blockMapping[currblock]*pSrcPart->erase_size, SEEK_SET)==-1) + { + close(fd); + bml_over_mtd_write_close(pSrcWrite); + bml_over_mtd_write_close(pResWrite); + fprintf(stderr, "flash_bml_partition: lseek in reservoir partition failed\n"); + return -1; + } + } + size_t blockBytesWritten = 0; + while (blockBytesWritten < pSrcPart->erase_size) + { +#ifdef DUMMY_WRITING + ssize_t len = write(srcFd, pSrcWrite->buffer + blockBytesWritten, + pSrcPart->erase_size - blockBytesWritten); +#else + ssize_t len = bml_over_mtd_write_block(srcFd, pSrcPart->erase_size, pSrcWrite->buffer); +#endif + if (len <= 0) + { + close(fd); + bml_over_mtd_write_close(pSrcWrite); + bml_over_mtd_write_close(pResWrite); + fprintf(stderr, "flash_bml_partition: writing to partition failed\n"); + return -1; + } + blockBytesWritten += len; + } + + + } + + bml_over_mtd_write_close(pSrcWrite); + bml_over_mtd_write_close(pResWrite); + + if (close(fd)) { + printf("error closing %s", filename); + return -1; + } + + return 0; +} + +static int scan_partition(const MtdPartition* pPart) +{ + BmlOverMtdReadContext* readCtx = bml_over_mtd_read_partition(pPart); + if (readCtx == NULL) + { + fprintf(stderr, "Failed to open partition for reading.\n"); + return -1; + } + + int numBadBlocks = 0; + size_t numBlocks = pPart->size / pPart->erase_size; + size_t currBlock; + for (currBlock = 0; currBlock < numBlocks; ++currBlock) + { + + loff_t pos = currBlock * pPart->erase_size; + int mgbb = ioctl(readCtx->fd, MEMGETBADBLOCK, &pos); + if (mgbb != 0) + { + printf("Bad block %d at 0x%x.\n", currBlock, (unsigned int)pos); + numBadBlocks++; + } + } + + bml_over_mtd_read_close(readCtx); + if (numBadBlocks == 0) + { + printf("No bad blocks.\n"); + return 0; + } + return -1 ; +} + +int main(int argc, char **argv) +{ + if (argc != 7 && (argc != 3 || (argc == 3 && strcmp(argv[1],"scan"))!=0) + && (argc != 6 || (argc == 6 && strcmp(argv[1],"scan"))!=0)) + return die("Usage: %s dump|flash <partition> <partition_start_block> <reservoirpartition> <reservoir_start_block> <file>\n" + "E.g. %s dump boot 72 reservoir 2004 file.bin\n" + "Usage: %s scan <partition> [<partition_start_block> <reservoirpartition> <reservoir_start_block>]\n" + ,argv[0], argv[0], argv[0]); + int num_partitions = mtd_scan_partitions(); + const MtdPartition *pSrcPart = mtd_find_partition_by_name(argv[2]); + if (pSrcPart == NULL) + return die("Cannot find partition %s", argv[2]); + + int scanResult = scan_partition(pSrcPart); + + if (argc == 3 && strcmp(argv[1],"scan")==0) + { + return (scanResult == 0 ? 0 : EXIT_CODE_BAD_BLOCKS); + } + + int retVal = 0; + const MtdPartition* pReservoirPart = mtd_find_partition_by_name(argv[4]); + if (pReservoirPart == NULL) + return die("Cannot find partition %s", argv[4]); + + int srcPartStartBlock = atoi(argv[3]); + int reservoirPartStartBlock = atoi(argv[5]); + const unsigned short* pMapping = CreateBlockMapping(pSrcPart, srcPartStartBlock, + pReservoirPart, reservoirPartStartBlock); + + if (pMapping == NULL && scanResult == 0) + { + printf("Generating empty block mapping table for error-free partition.\n"); + pMapping = CreateEmptyBlockMapping(pSrcPart); + } + + if (argc == 6 && strcmp(argv[1],"scan")==0) + { + retVal = (scanResult == 0 ? 0 : EXIT_CODE_BAD_BLOCKS); + } + + if (pMapping == NULL) + return die("Failed to create block mapping table"); + + if (strcmp(argv[1],"dump")==0) + { + retVal = dump_bml_partition(pSrcPart, pReservoirPart, pMapping, argv[6]); + if (retVal == 0) + printf("Successfully dumped partition to %s\n", argv[6]); + } + + if (strcmp(argv[1],"flash")==0) + { + retVal = flash_bml_partition(pSrcPart, pReservoirPart, pMapping, argv[6]); + if (retVal == 0) + printf("Successfully wrote %s to partition\n", argv[6]); + + } + + + ReleaseBlockMapping(pMapping); + return retVal; +} + diff --git a/mtdutils/mounts.c b/mtdutils/mounts.c new file mode 100644 index 000000000..cd3738a0b --- /dev/null +++ b/mtdutils/mounts.c @@ -0,0 +1,223 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <fcntl.h> +#include <errno.h> +#include <unistd.h> +#include <sys/mount.h> + +#include "mounts.h" + +struct MountedVolume { + const char *device; + const char *mount_point; + const char *filesystem; + const char *flags; +}; + +typedef struct { + MountedVolume *volumes; + int volumes_allocd; + int volume_count; +} MountsState; + +static MountsState g_mounts_state = { + NULL, // volumes + 0, // volumes_allocd + 0 // volume_count +}; + +static inline void +free_volume_internals(const MountedVolume *volume, int zero) +{ + free((char *)volume->device); + free((char *)volume->mount_point); + free((char *)volume->filesystem); + free((char *)volume->flags); + if (zero) { + memset((void *)volume, 0, sizeof(*volume)); + } +} + +#define PROC_MOUNTS_FILENAME "/proc/mounts" + +int +scan_mounted_volumes() +{ + char buf[2048]; + const char *bufp; + int fd; + ssize_t nbytes; + + if (g_mounts_state.volumes == NULL) { + const int numv = 32; + MountedVolume *volumes = malloc(numv * sizeof(*volumes)); + if (volumes == NULL) { + errno = ENOMEM; + return -1; + } + g_mounts_state.volumes = volumes; + g_mounts_state.volumes_allocd = numv; + memset(volumes, 0, numv * sizeof(*volumes)); + } else { + /* Free the old volume strings. + */ + int i; + for (i = 0; i < g_mounts_state.volume_count; i++) { + free_volume_internals(&g_mounts_state.volumes[i], 1); + } + } + g_mounts_state.volume_count = 0; + + /* Open and read the file contents. + */ + fd = open(PROC_MOUNTS_FILENAME, O_RDONLY); + if (fd < 0) { + goto bail; + } + nbytes = read(fd, buf, sizeof(buf) - 1); + close(fd); + if (nbytes < 0) { + goto bail; + } + buf[nbytes] = '\0'; + + /* Parse the contents of the file, which looks like: + * + * # cat /proc/mounts + * rootfs / rootfs rw 0 0 + * /dev/pts /dev/pts devpts rw 0 0 + * /proc /proc proc rw 0 0 + * /sys /sys sysfs rw 0 0 + * /dev/block/mtdblock4 /system yaffs2 rw,nodev,noatime,nodiratime 0 0 + * /dev/block/mtdblock5 /data yaffs2 rw,nodev,noatime,nodiratime 0 0 + * /dev/block/mmcblk0p1 /sdcard vfat rw,sync,dirsync,fmask=0000,dmask=0000,codepage=cp437,iocharset=iso8859-1,utf8 0 0 + * + * The zeroes at the end are dummy placeholder fields to make the + * output match Linux's /etc/mtab, but don't represent anything here. + */ + bufp = buf; + while (nbytes > 0) { + char device[64]; + char mount_point[64]; + char filesystem[64]; + char flags[128]; + int matches; + + /* %as is a gnu extension that malloc()s a string for each field. + */ + matches = sscanf(bufp, "%63s %63s %63s %127s", + device, mount_point, filesystem, flags); + + if (matches == 4) { + device[sizeof(device)-1] = '\0'; + mount_point[sizeof(mount_point)-1] = '\0'; + filesystem[sizeof(filesystem)-1] = '\0'; + flags[sizeof(flags)-1] = '\0'; + + MountedVolume *v = + &g_mounts_state.volumes[g_mounts_state.volume_count++]; + v->device = strdup(device); + v->mount_point = strdup(mount_point); + v->filesystem = strdup(filesystem); + v->flags = strdup(flags); + } else { +printf("matches was %d on <<%.40s>>\n", matches, bufp); + } + + /* Eat the line. + */ + while (nbytes > 0 && *bufp != '\n') { + bufp++; + nbytes--; + } + if (nbytes > 0) { + bufp++; + nbytes--; + } + } + + return 0; + +bail: +//TODO: free the strings we've allocated. + g_mounts_state.volume_count = 0; + return -1; +} + +const MountedVolume * +find_mounted_volume_by_device(const char *device) +{ + if (g_mounts_state.volumes != NULL) { + int i; + for (i = 0; i < g_mounts_state.volume_count; i++) { + MountedVolume *v = &g_mounts_state.volumes[i]; + /* May be null if it was unmounted and we haven't rescanned. + */ + if (v->device != NULL) { + if (strcmp(v->device, device) == 0) { + return v; + } + } + } + } + return NULL; +} + +const MountedVolume * +find_mounted_volume_by_mount_point(const char *mount_point) +{ + if (g_mounts_state.volumes != NULL) { + int i; + for (i = 0; i < g_mounts_state.volume_count; i++) { + MountedVolume *v = &g_mounts_state.volumes[i]; + /* May be null if it was unmounted and we haven't rescanned. + */ + if (v->mount_point != NULL) { + if (strcmp(v->mount_point, mount_point) == 0) { + return v; + } + } + } + } + return NULL; +} + +int +unmount_mounted_volume(const MountedVolume *volume) +{ + /* Intentionally pass NULL to umount if the caller tries + * to unmount a volume they already unmounted using this + * function. + */ + int ret = umount(volume->mount_point); + if (ret == 0) { + free_volume_internals(volume, 1); + return 0; + } + return ret; +} + +int +remount_read_only(const MountedVolume* volume) +{ + return mount(volume->device, volume->mount_point, volume->filesystem, + MS_NOATIME | MS_NODEV | MS_NODIRATIME | + MS_RDONLY | MS_REMOUNT, 0); +} diff --git a/mtdutils/mounts.h b/mtdutils/mounts.h new file mode 100644 index 000000000..ed7fb5fe3 --- /dev/null +++ b/mtdutils/mounts.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef MTDUTILS_MOUNTS_H_ +#define MTDUTILS_MOUNTS_H_ + +typedef struct { + const char *device; + const char *mount_point; + const char *filesystem; + const char *flags; +} MountedVolume; + +int scan_mounted_volumes(void); + +const MountedVolume *find_mounted_volume_by_device(const char *device); + +const MountedVolume * +find_mounted_volume_by_mount_point(const char *mount_point); + +int unmount_mounted_volume(const MountedVolume *volume); + +int remount_read_only(const MountedVolume* volume); + +#endif // MTDUTILS_MOUNTS_H_ diff --git a/mtdutils/mtdutils.c b/mtdutils/mtdutils.c new file mode 100644 index 000000000..7a22efe2e --- /dev/null +++ b/mtdutils/mtdutils.c @@ -0,0 +1,812 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <sys/mount.h> // for _IOW, _IOR, mount() +#include <sys/stat.h> +#include <mtd/mtd-user.h> +#undef NDEBUG +#include <assert.h> + +#include "mtdutils.h" + +#ifdef RK3X + #include "rk3xhack.h" +#endif + +#ifdef BYNAME +static const char mtdprefix[] = "/dev/block/mtd/by-name/"; +#define MTD_BASENAME_OFFSET (sizeof(mtdprefix)-1) +#endif + +struct MtdReadContext { + const MtdPartition *partition; + char *buffer; + size_t consumed; + int fd; +}; + +struct MtdWriteContext { + const MtdPartition *partition; + char *buffer; + size_t stored; + int fd; + + off_t* bad_block_offsets; + int bad_block_alloc; + int bad_block_count; +}; + +typedef struct { + MtdPartition *partitions; + int partitions_allocd; + int partition_count; +} MtdState; + +static MtdState g_mtd_state = { + NULL, // partitions + 0, // partitions_allocd + -1 // partition_count +}; + +#define MTD_PROC_FILENAME "/proc/mtd" + +int +mtd_scan_partitions() +{ + char buf[2048]; + const char *bufp; + int fd; + int i; + ssize_t nbytes; + + if (g_mtd_state.partitions == NULL) { + const int nump = 32; + MtdPartition *partitions = malloc(nump * sizeof(*partitions)); + if (partitions == NULL) { + errno = ENOMEM; + return -1; + } + g_mtd_state.partitions = partitions; + g_mtd_state.partitions_allocd = nump; + memset(partitions, 0, nump * sizeof(*partitions)); + } + g_mtd_state.partition_count = 0; + + /* Initialize all of the entries to make things easier later. + * (Lets us handle sparsely-numbered partitions, which + * may not even be possible.) + */ + for (i = 0; i < g_mtd_state.partitions_allocd; i++) { + MtdPartition *p = &g_mtd_state.partitions[i]; + if (p->name != NULL) { + free(p->name); + p->name = NULL; + } + p->device_index = -1; + } + + /* Open and read the file contents. + */ + fd = open(MTD_PROC_FILENAME, O_RDONLY); + if (fd < 0) { + goto bail; + } + nbytes = TEMP_FAILURE_RETRY(read(fd, buf, sizeof(buf) - 1)); + close(fd); + if (nbytes < 0) { + goto bail; + } + buf[nbytes] = '\0'; + + /* Parse the contents of the file, which looks like: + * + * # cat /proc/mtd + * dev: size erasesize name + * mtd0: 00080000 00020000 "bootloader" + * mtd1: 00400000 00020000 "mfg_and_gsm" + * mtd2: 00400000 00020000 "0000000c" + * mtd3: 00200000 00020000 "0000000d" + * mtd4: 04000000 00020000 "system" + * mtd5: 03280000 00020000 "userdata" + */ + bufp = buf; + while (nbytes > 0) { + int mtdnum, mtdsize, mtderasesize; + int matches; + char mtdname[64]; + mtdname[0] = '\0'; + mtdnum = -1; + + matches = sscanf(bufp, "mtd%d: %x %x \"%63[^\"]", + &mtdnum, &mtdsize, &mtderasesize, mtdname); + /* This will fail on the first line, which just contains + * column headers. + */ + if (matches == 4) { + MtdPartition *p = &g_mtd_state.partitions[mtdnum]; + p->device_index = mtdnum; + p->size = mtdsize; + p->erase_size = mtderasesize; +#ifdef BYNAME + asprintf(&p->name, "%s%s", mtdprefix, mtdname); +#else + p->name = strdup(mtdname); +#endif + if (p->name == NULL) { + errno = ENOMEM; + goto bail; + } + g_mtd_state.partition_count++; + } + + /* Eat the line. + */ + while (nbytes > 0 && *bufp != '\n') { + bufp++; + nbytes--; + } + if (nbytes > 0) { + bufp++; + nbytes--; + } + } + + return g_mtd_state.partition_count; + +bail: + // keep "partitions" around so we can free the names on a rescan. + g_mtd_state.partition_count = -1; + return -1; +} + +const MtdPartition * +mtd_find_partition_by_name(const char *name) +{ + if (g_mtd_state.partitions != NULL) { + int i; + for (i = 0; i < g_mtd_state.partitions_allocd; i++) { + MtdPartition *p = &g_mtd_state.partitions[i]; + if (p->device_index >= 0 && p->name != NULL) { + if (strcmp(p->name, name) == 0) { + return p; + } +#ifdef BYNAME + if (strcmp(p->name+MTD_BASENAME_OFFSET, name) == 0) { + return p; + } +#endif + } + } + } + return NULL; +} + +int +mtd_mount_partition(const MtdPartition *partition, const char *mount_point, + const char *filesystem, int read_only) +{ + const unsigned long flags = MS_NOATIME | MS_NODEV | MS_NODIRATIME; + char devname[64]; + int rv = -1; + + sprintf(devname, "/dev/block/mtdblock%d", partition->device_index); + if (!read_only) { + rv = mount(devname, mount_point, filesystem, flags, NULL); + } + if (read_only || rv < 0) { + rv = mount(devname, mount_point, filesystem, flags | MS_RDONLY, 0); + if (rv < 0) { + printf("Failed to mount %s on %s: %s\n", + devname, mount_point, strerror(errno)); + } else { + printf("Mount %s on %s read-only\n", devname, mount_point); + } + } +#if 1 //TODO: figure out why this is happening; remove include of stat.h + if (rv >= 0) { + /* For some reason, the x bits sometimes aren't set on the root + * of mounted volumes. + */ + struct stat st; + rv = stat(mount_point, &st); + if (rv < 0) { + return rv; + } + mode_t new_mode = st.st_mode | S_IXUSR | S_IXGRP | S_IXOTH; + if (new_mode != st.st_mode) { +printf("Fixing execute permissions for %s\n", mount_point); + rv = chmod(mount_point, new_mode); + if (rv < 0) { + printf("Couldn't fix permissions for %s: %s\n", + mount_point, strerror(errno)); + } + } + } +#endif + return rv; +} + +int +mtd_partition_info(const MtdPartition *partition, + size_t *total_size, size_t *erase_size, size_t *write_size) +{ + char mtddevname[32]; + sprintf(mtddevname, "/dev/mtd/mtd%d", partition->device_index); + int fd = open(mtddevname, O_RDONLY); + if (fd < 0) return -1; + + struct mtd_info_user mtd_info; + int ret = ioctl(fd, MEMGETINFO, &mtd_info); + close(fd); + if (ret < 0) return -1; + + if (total_size != NULL) *total_size = mtd_info.size; + if (erase_size != NULL) *erase_size = mtd_info.erasesize; + if (write_size != NULL) *write_size = mtd_info.writesize; + return 0; +} + +MtdReadContext *mtd_read_partition(const MtdPartition *partition) +{ + MtdReadContext *ctx = (MtdReadContext*) malloc(sizeof(MtdReadContext)); + if (ctx == NULL) return NULL; + + ctx->buffer = malloc(partition->erase_size); + if (ctx->buffer == NULL) { + free(ctx); + return NULL; + } + + char mtddevname[32]; + sprintf(mtddevname, "/dev/mtd/mtd%d", partition->device_index); + ctx->fd = open(mtddevname, O_RDONLY); + if (ctx->fd < 0) { + free(ctx->buffer); + free(ctx); + return NULL; + } + + ctx->partition = partition; + ctx->consumed = partition->erase_size; + return ctx; +} + +static int read_block(const MtdPartition *partition, int fd, char *data) +{ + struct mtd_ecc_stats before, after; + if (ioctl(fd, ECCGETSTATS, &before)) { + printf("mtd: ECCGETSTATS error (%s)\n", strerror(errno)); + return -1; + } + + loff_t pos = TEMP_FAILURE_RETRY(lseek64(fd, 0, SEEK_CUR)); + if (pos == -1) { + printf("mtd: read_block: couldn't SEEK_CUR: %s\n", strerror(errno)); + return -1; + } + + ssize_t size = partition->erase_size; + int mgbb; + + while (pos + size <= (int) partition->size) { + if (TEMP_FAILURE_RETRY(lseek64(fd, pos, SEEK_SET)) != pos || + TEMP_FAILURE_RETRY(read(fd, data, size)) != size) { + printf("mtd: read error at 0x%08llx (%s)\n", + (long long)pos, strerror(errno)); + } else if (ioctl(fd, ECCGETSTATS, &after)) { + printf("mtd: ECCGETSTATS error (%s)\n", strerror(errno)); + return -1; + } else if (after.failed != before.failed) { + printf("mtd: ECC errors (%d soft, %d hard) at 0x%08llx\n", + after.corrected - before.corrected, + after.failed - before.failed, (long long)pos); + // copy the comparison baseline for the next read. + memcpy(&before, &after, sizeof(struct mtd_ecc_stats)); + } else if ((mgbb = ioctl(fd, MEMGETBADBLOCK, &pos))) { + fprintf(stderr, + "mtd: MEMGETBADBLOCK returned %d at 0x%08llx: %s\n", + mgbb, (long long)pos, strerror(errno)); + } else { + return 0; // Success! + } + + pos += partition->erase_size; + } + + errno = ENOSPC; + return -1; +} + +ssize_t mtd_read_data(MtdReadContext *ctx, char *data, size_t len) +{ + ssize_t read = 0; + while (read < (int) len) { + if (ctx->consumed < ctx->partition->erase_size) { + size_t avail = ctx->partition->erase_size - ctx->consumed; + size_t copy = len - read < avail ? len - read : avail; + memcpy(data + read, ctx->buffer + ctx->consumed, copy); + ctx->consumed += copy; + read += copy; + } + + // Read complete blocks directly into the user's buffer + while (ctx->consumed == ctx->partition->erase_size && + len - read >= ctx->partition->erase_size) { + if (read_block(ctx->partition, ctx->fd, data + read)) return -1; + read += ctx->partition->erase_size; + } + + if (read >= (int)len) { + return read; + } + + // Read the next block into the buffer + if (ctx->consumed == ctx->partition->erase_size && read < (int) len) { + if (read_block(ctx->partition, ctx->fd, ctx->buffer)) return -1; + ctx->consumed = 0; + } + } + + return read; +} + +void mtd_read_close(MtdReadContext *ctx) +{ + close(ctx->fd); + free(ctx->buffer); + free(ctx); +} + +MtdWriteContext *mtd_write_partition(const MtdPartition *partition) +{ + MtdWriteContext *ctx = (MtdWriteContext*) malloc(sizeof(MtdWriteContext)); + if (ctx == NULL) return NULL; + + ctx->bad_block_offsets = NULL; + ctx->bad_block_alloc = 0; + ctx->bad_block_count = 0; + + ctx->buffer = malloc(partition->erase_size); + if (ctx->buffer == NULL) { + free(ctx); + return NULL; + } + + char mtddevname[32]; + sprintf(mtddevname, "/dev/mtd/mtd%d", partition->device_index); + ctx->fd = open(mtddevname, O_RDWR); + if (ctx->fd < 0) { + free(ctx->buffer); + free(ctx); + return NULL; + } + + ctx->partition = partition; + ctx->stored = 0; + return ctx; +} + +static void add_bad_block_offset(MtdWriteContext *ctx, off_t pos) { + if (ctx->bad_block_count + 1 > ctx->bad_block_alloc) { + ctx->bad_block_alloc = (ctx->bad_block_alloc*2) + 1; + ctx->bad_block_offsets = realloc(ctx->bad_block_offsets, + ctx->bad_block_alloc * sizeof(off_t)); + } + ctx->bad_block_offsets[ctx->bad_block_count++] = pos; +} + +static int write_block(MtdWriteContext *ctx, const char *data) +{ + const MtdPartition *partition = ctx->partition; + int fd = ctx->fd; + + off_t pos = TEMP_FAILURE_RETRY(lseek(fd, 0, SEEK_CUR)); + if (pos == (off_t) -1) { + printf("mtd: write_block: couldn't SEEK_CUR: %s\n", strerror(errno)); + return -1; + } + + ssize_t size = partition->erase_size; + while (pos + size <= (int) partition->size) { + loff_t bpos = pos; + int ret = ioctl(fd, MEMGETBADBLOCK, &bpos); + if (ret != 0 && !(ret == -1 && errno == EOPNOTSUPP)) { + add_bad_block_offset(ctx, pos); + fprintf(stderr, + "mtd: not writing bad block at 0x%08lx (ret %d): %s\n", + pos, ret, strerror(errno)); + pos += partition->erase_size; + continue; // Don't try to erase known factory-bad blocks. + } + + struct erase_info_user erase_info; + erase_info.start = pos; + erase_info.length = size; + int retry; + for (retry = 0; retry < 2; ++retry) { +#ifdef RK3X + if (rk30_zero_out(fd, pos, size) < 0) { + fprintf(stderr, "mtd: erase failure at 0x%08lx (%s)\n", + pos, strerror(errno)); + continue; + } +#else + if (ioctl(fd, MEMERASE, &erase_info) < 0) { + printf("mtd: erase failure at 0x%08lx (%s)\n", + pos, strerror(errno)); + continue; + } +#endif + if (TEMP_FAILURE_RETRY(lseek(fd, pos, SEEK_SET)) != pos || + TEMP_FAILURE_RETRY(write(fd, data, size)) != size) { + printf("mtd: write error at 0x%08lx (%s)\n", + pos, strerror(errno)); + } + + char verify[size]; + if (TEMP_FAILURE_RETRY(lseek(fd, pos, SEEK_SET)) != pos || + TEMP_FAILURE_RETRY(read(fd, verify, size)) != size) { + printf("mtd: re-read error at 0x%08lx (%s)\n", + pos, strerror(errno)); + continue; + } + if (memcmp(data, verify, size) != 0) { + printf("mtd: verification error at 0x%08lx (%s)\n", + pos, strerror(errno)); + continue; + } + + if (retry > 0) { + printf("mtd: wrote block after %d retries\n", retry); + } + printf("mtd: successfully wrote block at %lx\n", pos); + return 0; // Success! + } + + // Try to erase it once more as we give up on this block + add_bad_block_offset(ctx, pos); + printf("mtd: skipping write block at 0x%08lx\n", pos); +#ifdef RK3X + rk30_zero_out(fd, pos, size); +#else + + ioctl(fd, MEMERASE, &erase_info); +#endif + pos += partition->erase_size; + } + + // Ran out of space on the device + errno = ENOSPC; + return -1; +} + +ssize_t mtd_write_data(MtdWriteContext *ctx, const char *data, size_t len) +{ + size_t wrote = 0; + while (wrote < len) { + // Coalesce partial writes into complete blocks + if (ctx->stored > 0 || len - wrote < ctx->partition->erase_size) { + size_t avail = ctx->partition->erase_size - ctx->stored; + size_t copy = len - wrote < avail ? len - wrote : avail; + memcpy(ctx->buffer + ctx->stored, data + wrote, copy); + ctx->stored += copy; + wrote += copy; + } + + // If a complete block was accumulated, write it + if (ctx->stored == ctx->partition->erase_size) { + if (write_block(ctx, ctx->buffer)) return -1; + ctx->stored = 0; + } + + // Write complete blocks directly from the user's buffer + while (ctx->stored == 0 && len - wrote >= ctx->partition->erase_size) { + if (write_block(ctx, data + wrote)) return -1; + wrote += ctx->partition->erase_size; + } + } + + return wrote; +} + +off_t mtd_erase_blocks(MtdWriteContext *ctx, int blocks) +{ + // Zero-pad and write any pending data to get us to a block boundary + if (ctx->stored > 0) { + size_t zero = ctx->partition->erase_size - ctx->stored; + memset(ctx->buffer + ctx->stored, 0, zero); + if (write_block(ctx, ctx->buffer)) return -1; + ctx->stored = 0; + } + + off_t pos = TEMP_FAILURE_RETRY(lseek(ctx->fd, 0, SEEK_CUR)); + if ((off_t) pos == (off_t) -1) { + printf("mtd_erase_blocks: couldn't SEEK_CUR: %s\n", strerror(errno)); + return -1; + } + + const int total = (ctx->partition->size - pos) / ctx->partition->erase_size; + if (blocks < 0) blocks = total; + if (blocks > total) { + errno = ENOSPC; + return -1; + } + + // Erase the specified number of blocks + while (blocks-- > 0) { + loff_t bpos = pos; + if (ioctl(ctx->fd, MEMGETBADBLOCK, &bpos) > 0) { + printf("mtd: not erasing bad block at 0x%08lx\n", pos); + pos += ctx->partition->erase_size; + continue; // Don't try to erase known factory-bad blocks. + } + + struct erase_info_user erase_info; + erase_info.start = pos; + erase_info.length = ctx->partition->erase_size; +#ifdef RK3X + if (rk30_zero_out(ctx->fd, pos, ctx->partition->erase_size) < 0) { + fprintf(stderr, "mtd: erase failure at 0x%08lx\n", pos); + } +#else + if (ioctl(ctx->fd, MEMERASE, &erase_info) < 0) { + printf("mtd: erase failure at 0x%08lx\n", pos); + } +#endif + pos += ctx->partition->erase_size; + } + + return pos; +} + +int mtd_write_close(MtdWriteContext *ctx) +{ + int r = 0; + // Make sure any pending data gets written + if (mtd_erase_blocks(ctx, 0) == (off_t) -1) r = -1; + if (close(ctx->fd)) r = -1; + free(ctx->bad_block_offsets); + free(ctx->buffer); + free(ctx); + return r; +} + +/* Return the offset of the first good block at or after pos (which + * might be pos itself). + */ +off_t mtd_find_write_start(MtdWriteContext *ctx, off_t pos) { + int i; + for (i = 0; i < ctx->bad_block_count; ++i) { + if (ctx->bad_block_offsets[i] == pos) { + pos += ctx->partition->erase_size; + } else if (ctx->bad_block_offsets[i] > pos) { + return pos; + } + } + return pos; +} + +#define BLOCK_SIZE 2048 +#define SPARE_SIZE (BLOCK_SIZE >> 5) +#define HEADER_SIZE 2048 + +int cmd_mtd_restore_raw_partition(const char *partition_name, const char *filename) +{ + const MtdPartition *ptn; + MtdWriteContext *write; + void *data; + + FILE* f = fopen(filename, "rb"); + if (f == NULL) { + fprintf(stderr, "error opening %s", filename); + return -1; + } + + if (mtd_scan_partitions() <= 0) + { + fprintf(stderr, "error scanning partitions"); + return -1; + } + const MtdPartition *mtd = mtd_find_partition_by_name(partition_name); + if (mtd == NULL) + { + fprintf(stderr, "can't find %s partition", partition_name); + return -1; + } + + int fd = open(filename, O_RDONLY); + if (fd < 0) + { + printf("error opening %s", filename); + return -1; + } + + MtdWriteContext* ctx = mtd_write_partition(mtd); + if (ctx == NULL) { + printf("error writing %s", partition_name); + return -1; + } + + int success = 1; + char* buffer = malloc(BUFSIZ); + int read; + while (success && (read = fread(buffer, 1, BUFSIZ, f)) > 0) { + int wrote = mtd_write_data(ctx, buffer, read); + success = success && (wrote == read); + } + free(buffer); + fclose(f); + + if (!success) { + fprintf(stderr, "error writing %s", partition_name); + return -1; + } + + if (mtd_erase_blocks(ctx, -1) == -1) { + fprintf(stderr, "error erasing blocks of %s\n", partition_name); + } + if (mtd_write_close(ctx) != 0) { + fprintf(stderr, "error closing write of %s\n", partition_name); + } + printf("%s %s partition\n", success ? "wrote" : "failed to write", partition_name); + return 0; +} + + +int cmd_mtd_backup_raw_partition(const char *partition_name, const char *filename) +{ + MtdReadContext *in; + const MtdPartition *partition; + char buf[BLOCK_SIZE + SPARE_SIZE]; + size_t partition_size; + size_t read_size; + size_t total; + int fd; + int wrote; + int len; + + if (mtd_scan_partitions() <= 0) + { + printf("error scanning partitions"); + return -1; + } + + partition = mtd_find_partition_by_name(partition_name); + if (partition == NULL) + { + printf("can't find %s partition", partition_name); + return -1; + } + + if (mtd_partition_info(partition, &partition_size, NULL, NULL)) { + printf("can't get info of partition %s", partition_name); + return -1; + } + + if (!strcmp(filename, "-")) { + fd = fileno(stdout); + } + else { + fd = open(filename, O_WRONLY|O_CREAT|O_TRUNC, 0666); + } + + if (fd < 0) + { + printf("error opening %s", filename); + return -1; + } + + in = mtd_read_partition(partition); + if (in == NULL) { + close(fd); + unlink(filename); + printf("error opening %s: %s\n", partition_name, strerror(errno)); + return -1; + } + + total = 0; + while ((len = mtd_read_data(in, buf, BLOCK_SIZE)) > 0) { + wrote = write(fd, buf, len); + if (wrote != len) { + close(fd); + unlink(filename); + printf("error writing %s", filename); + return -1; + } + total += BLOCK_SIZE; + } + + mtd_read_close(in); + + if (close(fd)) { + unlink(filename); + printf("error closing %s", filename); + return -1; + } + return 0; +} + +int cmd_mtd_erase_raw_partition(const char *partition_name) +{ + MtdWriteContext *out; + size_t erased; + size_t total_size; + size_t erase_size; + + if (mtd_scan_partitions() <= 0) + { + printf("error scanning partitions"); + return -1; + } + const MtdPartition *p = mtd_find_partition_by_name(partition_name); + if (p == NULL) + { + printf("can't find %s partition", partition_name); + return -1; + } + + out = mtd_write_partition(p); + if (out == NULL) + { + printf("could not estabilish write context for %s", partition_name); + return -1; + } + + // do the actual erase, -1 = full partition erase + erased = mtd_erase_blocks(out, -1); + + // erased = bytes erased, if zero, something borked + if (!erased) + { + printf("error erasing %s", partition_name); + return -1; + } + + return 0; +} + +int cmd_mtd_erase_partition(const char *partition, const char *filesystem) +{ + return cmd_mtd_erase_raw_partition(partition); +} + + +int cmd_mtd_mount_partition(const char *partition, const char *mount_point, const char *filesystem, int read_only) +{ + mtd_scan_partitions(); + const MtdPartition *p; + p = mtd_find_partition_by_name(partition); + if (p == NULL) { + return -1; + } + return mtd_mount_partition(p, mount_point, filesystem, read_only); +} + +int cmd_mtd_get_partition_device(const char *partition, char *device) +{ + mtd_scan_partitions(); + MtdPartition *p = mtd_find_partition_by_name(partition); + if (p == NULL) + return -1; + sprintf(device, "/dev/block/mtdblock%d", p->device_index); + return 0; +}
\ No newline at end of file diff --git a/mtdutils/mtdutils.h b/mtdutils/mtdutils.h new file mode 100644 index 000000000..235cbe7ce --- /dev/null +++ b/mtdutils/mtdutils.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef MTDUTILS_H_ +#define MTDUTILS_H_ + +#include <sys/types.h> // for size_t, etc. + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct MtdPartition MtdPartition; + +int mtd_scan_partitions(void); + +const MtdPartition *mtd_find_partition_by_name(const char *name); + +/* mount_point is like "/system" + * filesystem is like "yaffs2" + */ +int mtd_mount_partition(const MtdPartition *partition, const char *mount_point, + const char *filesystem, int read_only); + +/* get the partition and the minimum erase/write block size. NULL is ok. + */ +int mtd_partition_info(const MtdPartition *partition, + size_t *total_size, size_t *erase_size, size_t *write_size); + +/* read or write raw data from a partition, starting at the beginning. + * skips bad blocks as best we can. + */ +typedef struct MtdReadContext MtdReadContext; +typedef struct MtdWriteContext MtdWriteContext; + +MtdReadContext *mtd_read_partition(const MtdPartition *); +ssize_t mtd_read_data(MtdReadContext *, char *data, size_t data_len); +void mtd_read_close(MtdReadContext *); + +MtdWriteContext *mtd_write_partition(const MtdPartition *); +ssize_t mtd_write_data(MtdWriteContext *, const char *data, size_t data_len); +off_t mtd_erase_blocks(MtdWriteContext *, int blocks); /* 0 ok, -1 for all */ +int mtd_write_close(MtdWriteContext *); + +struct MtdPartition { + int device_index; + unsigned int size; + unsigned int erase_size; + char *name; +}; + +#ifdef __cplusplus +} +#endif + +#endif // MTDUTILS_H_ diff --git a/mtdutils/rk3xhack.c b/mtdutils/rk3xhack.c new file mode 100644 index 000000000..930f60593 --- /dev/null +++ b/mtdutils/rk3xhack.c @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2013, Sergey 'Jin' Bostandzhyan + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* This is a hack for Rockchip rk3x based devices. The problem is that + * the MEMERASE ioctl is failing (hangs and never returns) in their kernel. + * The sources are not fully available, so fixing it in the rk30xxnand_ko driver + * is not possible. + * + * I straced the stock recovery application and it seems to avoid this + * particular ioctl, instead it is simply writing zeroes using the write() call. + * + * This workaround does the same and will replace all MEMERASE occurances in + * the recovery code. + */ + +#include <sys/types.h> +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> +#include <errno.h> + +#include "rk3xhack.h" + +int rk30_zero_out(int fd, off_t pos, ssize_t size) +{ + if (lseek(fd, pos, SEEK_SET) != pos) { + fprintf(stderr, "mtd: erase failure at 0x%08lx (%s)\n", + pos, strerror(errno)); + return -1; + } + + unsigned char *zb = (unsigned char *)calloc(1, size); + if (zb == NULL) { + fprintf(stderr, "mtd: erase failure, could not allocate memory\n"); + return -1; + } + + if (write(fd, zb, size) != size) { + fprintf(stderr, "mtd: erase failure at 0x%08lx (%s)\n", + pos, strerror(errno)); + free(zb); + return -1; + } + + free(zb); + return 0; +} diff --git a/mtdutils/rk3xhack.h b/mtdutils/rk3xhack.h new file mode 100644 index 000000000..3cc16e49e --- /dev/null +++ b/mtdutils/rk3xhack.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2013 Sergey 'Jin' Bostandzhyan + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* This is a hack for Rockchip rk3x based devices. The problem is that + * the MEMERASE ioctl is failing (hangs and never returns) in their kernel. + * The sources are not fully available, so fixing it in the rk30xxnand_ko driver + * is not possible. + * + * I straced the stock recovery application and it seems to avoid this + * particular ioctl, instead it is simply writing zeroes using the write() call. + * + * This workaround does the same and will replace all MEMERASE occurances in + * the recovery code. + */ + +#ifndef __RK3X_HACK_H__ +#define __RK3X_HACK_H__ + +#include <sys/types.h> // for size_t, etc. + +// write zeroes to fd at position pos +int zero_out(int fd, off_t pos, ssize_t length); + +#endif//__RK3X_HACK_H__ |