/* * 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 #include #include #include #include #include #include #include #include #include #include "cutils/log.h" #include #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 \n" "E.g. %s dump boot 72 reservoir 2004 file.bin\n" "Usage: %s scan [ ]\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; }