/* * mkyaffs2.c - erase and write utility for YAFFS2 * * SeonKon Choi * * 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. */ /* * tested with 128MiB partition on OneNAND(2048/64), linux 2.6.16.11 */ #define _XOPEN_SOURCE 500 #define _GNU_SOURCE #define _LARGEFILE_SOURCE #define _LARGEFILE64_SOURCE #define _FILE_OFFSET_BITS 64 #include #include #include #include #include #include #include #include #include #include #include #include #include #define __user #include "mtd/mtd-user.h" #define MAX_DATA_SIZE (2048) #define MAX_OOB_SIZE (64) #define YAFFS2_TAG_SIZE (28) /* sizeof(yaffs_PackedTags2) */ #ifdef ECCGETLAYOUT /* linux > 2.6.16 */ /* * I think, this is needless. compatability was implemented in mtdchar.c. * but, I want to test new API. */ typedef struct nand_ecclayout mk_ooblayout; # define MK_MTD_GET_OOBLAYOUT ECCGETLAYOUT # warning "using ECCGETLAYOUT" #else typedef struct nand_oobinfo mk_ooblayout; # define MK_MTD_GET_OOBLAYOUT MEMGETOOBSEL # warning "using MEMGETOOBSEL" #endif struct mk_info { int mtd_fd; int img_fd; size_t img_size; int mtd_oob_avail; mk_ooblayout *ooblayout; struct mtd_info_user *mtdinfo; }; static char *mtd_device = NULL; static char *img = NULL; static int *bbt_buf = NULL; static int force_erase = 0; static int mark_bad = 0; static int do_not_erase = 0; static int erase_only = 0; static int do_not_verify = 0; static unsigned char ffchars[MAX_DATA_SIZE]; void __print_1_line(char *p, int end, int num) { int i; printf("%03X0 | ", num & 0xffff); for (i = 0; i< 16; i++) { if (i==8) printf("- "); if (i < end) printf("%02X ", *(p+i) & 0xff); else printf(" "); } printf(" "); for (i = 0; i< 16; i++) { if (i==8) printf(" - "); if (i < end) { if (isprint(*(p+i))) printf("%c", *(p+i) & 0xff); else printf("."); } else printf(" "); } printf("\n"); } void t_dump_data(char *buf, int size) { int i = 0; char *p = buf; int lines = size / 16; int mod = size % 16; printf("-----+--------------------------------------------------\n"); for (i = 0; i < lines; i++) { __print_1_line(p, 16, i); p += 16; } if (mod) __print_1_line(p, mod, i); printf("-----+--------------------------------------------------\n"); } #define MIN(a,b) ((a)>(b) ? (b):(a)) int oob_to_chip_layout(unsigned char *in, unsigned char *out, int length, mk_ooblayout *ooblayout) { int i; int left = length; int to, len, thislen; #ifdef ECCGETLAYOUT struct nand_oobfree *oobfree; for (i = 0; (i < MTD_MAX_OOBFREE_ENTRIES) && left; i++) { oobfree = &ooblayout->oobfree[i]; if (!oobfree->length) break; to = (int)oobfree->offset; len = (int)oobfree->length; #else if (ooblayout->useecc != MTD_NANDECC_AUTOPLACE) { memcpy(out, in, length); return 0; } for (i = 0; ooblayout->oobfree[i][1] && left; i++) { to = ooblayout->oobfree[i][0]; len = ooblayout->oobfree[i][1]; #endif thislen = MIN(left, len); memcpy(out + to, in, thislen); left -= thislen; in += thislen; } if (left) { /* robust */ fprintf(stderr, "not enough oob space. ooblayout changed ?\n"); return -1; } return 0; } int oob_from_chip_layout(unsigned char *in, unsigned char *out, int length, mk_ooblayout *ooblayout) { int i; int left = length; int from, len, thislen; #ifdef ECCGETLAYOUT struct nand_oobfree *oobfree; for (i = 0; (i < MTD_MAX_OOBFREE_ENTRIES) && left; i++) { oobfree = &ooblayout->oobfree[i]; if (!oobfree->length) break; from = (int)oobfree->offset; len = (int)oobfree->length; #else if (ooblayout->useecc != MTD_NANDECC_AUTOPLACE) { memcpy(out, in, length); return 0; } for (i = 0; ooblayout->oobfree[i][1] && left; i++) { from = ooblayout->oobfree[i][0]; len = ooblayout->oobfree[i][1]; #endif thislen = MIN(left, len); memcpy(out, in + from, thislen); left -= thislen; out += thislen; } if (left) { /* robust */ fprintf(stderr, "not enough oob space. ooblayout changed ?\n"); return -1; } return 0; } int mtd_mark_badblock(struct mk_info *info, off_t block_num) { int ret = -1; off_t mtd_offset = info->mtdinfo->erasesize * block_num; ret = ioctl(info->mtd_fd, MEMSETBADBLOCK, &mtd_offset); if (ret < 0) { fprintf(stderr, "MEMSETBADBLOCK at %lld th block: %s\n", (long long)block_num, strerror(errno)); return ret; } return 0; } void reset_bbt(int *bbt) { if (!bbt) fprintf(stderr, "no bbt\n"); *bbt = -1; } int scan_bbt(struct mk_info *info, int *bbt) { unsigned int mtd_block_size = info->mtdinfo->erasesize; unsigned int mtd_blocks = info->mtdinfo->size / mtd_block_size; unsigned int i; off_t mtd_offset; int ret = -1; int bad_num = 0; memset(bbt, 0x00, info->mtdinfo->size * sizeof(int) / mtd_block_size); reset_bbt(bbt); for (i = 0; i < mtd_blocks; i++, bbt++) { mtd_offset = (off_t)(i * mtd_block_size); ret = ioctl(info->mtd_fd, MEMGETBADBLOCK, &mtd_offset); if (ret < 0) { fprintf(stderr, "MEMGETBADBLOCK at %u th block: %s\n", i, strerror(errno)); return ret; } if (ret > 0) { *bbt = 1; bad_num ++; } else { *bbt = 0; } } return bad_num; } int inline is_badblock(off_t block_num) { if (!bbt_buf || (bbt_buf[0] == -1)) fprintf(stderr, "WARNING: no bbt or not scaned.\n"); return bbt_buf[(int)block_num]; } int mtd_erase_block(struct mk_info *info, unsigned int block_num) { erase_info_t erase; unsigned char buf[MAX_DATA_SIZE]; int read_len; struct mtd_oob_buf oob; int errcount = 0; unsigned int data_len, oob_len; data_len = info->mtdinfo->writesize; oob_len = info->mtdinfo->oobsize; erase.start = (uint32_t)(block_num * info->mtdinfo->erasesize); erase.length = info->mtdinfo->erasesize; if (ioctl(info->mtd_fd, MEMERASE, &erase) != 0) { fprintf(stderr, "%u th block, erase failed: %s\n", (uint32_t)block_num, strerror(errno)); return -1; } /* read first oob for erase verify */ memset(&buf[0], 0x55, sizeof(buf)); oob.start = (uint32_t)(block_num * info->mtdinfo->erasesize); oob.length = (uint32_t)oob_len; oob.ptr = &buf[0]; read_len = ioctl(info->mtd_fd, MEMREADOOB, &oob); if (read_len < 0) { perror ("ioctl(MEMREADOOB)"); return -1; } /* check returned value of MEMREADOOB */ if (oob.start != oob_len) { fprintf(stderr, "\n*ret_len wrong on MEMREADOOB\n"); return -1; } /* oob erase verify. sometimes failed on OneNAND */ if (memcmp(&ffchars[0], &buf[0], oob_len)) { fprintf(stderr, "%u th block oob erase verify failed\n", block_num); t_dump_data((char*)&buf[0], oob_len); errcount |= 2; } #if 0 /* * curious, data area is not erased on OneNAND. * and ECC error (-EBADMSG) is not reported when reading. */ /* read first data for erase verify */ read_len = pread(info->mtd_fd, &buf[0], data_len, (off_t)(block_num * info->mtdinfo->erasesize)); if (read_len != data_len) { if (read_len < 0) { fprintf(stderr, "%u th block read: %s\n", block_num, strerror(errno)); if (errno == EBADMSG) { /* on ECC error, kernel returns data for analyze */ fprintf(stderr, "ECC error occured\n"); t_dump_data((char*)&buf[0], data_len); } return -1; } fprintf(stderr, "%u th block read failed, %d\n", block_num, read_len); return -1; } /* data erase verify, always failed on OneNAND */ if (memcmp(&ffchars[0], &buf[0], data_len)) { fprintf(stderr, "%u th block data erase verify failed\n", block_num); t_dump_data((char*)&buf[0], data_len); errcount |= 1; } #endif if (errcount) { // return -1; } return 0; } int erase_all(struct mk_info *info, int force) { unsigned int mtd_block_size = info->mtdinfo->erasesize; unsigned int mtd_blocks = info->mtdinfo->size / mtd_block_size; unsigned int i; int ret = -1; for (i = 0; i < mtd_blocks; i++) { if (!force && is_badblock(i)) continue; ret = mtd_erase_block(info, i); if (ret < 0) { goto out; } printf("\r%u/%u ", i+1, mtd_blocks); fflush(stdout); } ret = 0; out: printf("\n"); return ret; } int process_file(struct mk_info *info, int mark, int noverify) { struct mtd_oob_buf oob; unsigned char data_writebuf[MAX_DATA_SIZE]; unsigned char data_readbuf[MAX_DATA_SIZE]; unsigned char oob_writebuf[MAX_OOB_SIZE]; unsigned char oob_readbuf[MAX_OOB_SIZE]; unsigned char tmpbuf[MAX_OOB_SIZE]; int mtd_block_len; /* block size */ int mtd_pages; /* pages per block */ int mtd_blocks; /* number of block of device */ off_t mtd_block_num = 0; /* 0 ~ (mtd_blocks - 1) */ off_t mtd_page_num = 0; /* 0 ~ (mtd_pages - 1) */ off_t mtd_offset = 0; /* read and write offset of device */ off_t img_offset = 0; /* read offset of file */ int mtd_ret; /* read, write, ioctl return value of device */ size_t read_dlen; size_t read_olen; int ret = EXIT_FAILURE; size_t data_len, oob_len, page_len; int verify_failed; #define DUMP_DATA_READBUF (1<<0) #define DUMP_DATA_WRITEBUF (1<<1) #define DUMP_OOB_READBUF (1<<2) #define DUMP_OOB_WRITEBUF (1<<3) data_len = info->mtdinfo->writesize; oob_len = info->mtdinfo->oobsize; mtd_block_len = info->mtdinfo->erasesize; mtd_blocks = info->mtdinfo->size / mtd_block_len; mtd_pages = mtd_block_len / data_len; page_len = data_len + oob_len; do { /* read from file */ read_dlen = pread(info->img_fd, &data_writebuf[0], data_len, img_offset); read_olen = pread(info->img_fd, &oob_writebuf[0], oob_len, img_offset + data_len); if ((read_dlen != data_len) || (read_olen != oob_len)) { break; } if (mtd_block_num >= mtd_blocks) { printf("\nnot enough space\n"); goto out; } /* check if badblock */ if (!mtd_page_num) { while(is_badblock(mtd_block_num)) { putchar('B'); mtd_block_num ++; mtd_page_num = 0; if (mtd_block_num >= mtd_blocks) { fprintf(stderr, "not enough space.\n"); goto out; } } putchar('#'); fflush(stdout); fflush(stderr); } /* calculate write offset of device */ mtd_offset = mtd_block_num * mtd_block_len + mtd_page_num * data_len; /* place by oob layout of chip */ memset(&tmpbuf[0], 0xff, sizeof(tmpbuf)); if (oob_to_chip_layout(&oob_writebuf[0], &tmpbuf[0], YAFFS2_TAG_SIZE, info->ooblayout)) { goto out; } /* write oob */ oob.start = (uint32_t)mtd_offset; oob.length = (uint32_t)oob_len; oob.ptr = &tmpbuf[0]; mtd_ret = ioctl(info->mtd_fd, MEMWRITEOOB, &oob); if (mtd_ret < 0) { perror ("ioctl(MEMWRITEOOB)"); goto out; } /* check returned value of MEMWRITEOOB */ if (oob.length != oob_len) { fprintf(stderr, "*ret_len wrong on MEMWRITEOOB\n"); goto out; } /* write data */ mtd_ret = pwrite(info->mtd_fd, &data_writebuf[0], data_len, mtd_offset); if (mtd_ret != data_len) { fprintf(stderr,"write %lld@%lld: %s\n", (long long)mtd_page_num, (long long)mtd_block_num, strerror(errno)); goto out; } if (noverify) { /* jump to next page */ goto next_page; } verify_failed = 0; /* readback oob for verify */ memset(&oob_readbuf[0], 0x55, sizeof(oob_readbuf)); oob.start = (uint32_t)mtd_offset; oob.length = (uint32_t)oob_len; oob.ptr = &tmpbuf[0]; mtd_ret = ioctl(info->mtd_fd, MEMREADOOB, &oob); if (mtd_ret < 0) { perror ("ioctl(MEMREADOOB)"); goto out; } /* check returned value of MEMREADOOB */ if (oob.start != oob_len) { fprintf(stderr, "*ret_len wrong on MEMREADOOB\n"); goto out; } /* place by oob layout of chip */ if (oob_from_chip_layout(&tmpbuf[0], &oob_readbuf[0], YAFFS2_TAG_SIZE, info->ooblayout)) { goto out; } /* readback data for verify */ mtd_ret = pread(info->mtd_fd, &data_readbuf[0], data_len, mtd_offset); if (mtd_ret != data_len) { fprintf(stderr,"read %lld@%lld: %s\n", (long long)mtd_page_num, (long long)mtd_block_num, strerror(errno)); if ((mtd_ret < 0) && (errno == EBADMSG)) { /* on ECC error, kernel returns data for analyze */ fprintf(stderr, "ECC error occured\n"); verify_failed |= DUMP_DATA_READBUF; goto mark_bad_and_skip; } goto out; } /* verify oob */ if (memcmp(&oob_writebuf[0], &oob_readbuf[0], YAFFS2_TAG_SIZE)) { fprintf(stderr, "%lld th block, %lld th page, OOB verify error\n", (long long)mtd_block_num, (long long)mtd_page_num); verify_failed |= (DUMP_OOB_READBUF | DUMP_OOB_WRITEBUF); } /* verify data */ if (memcmp(&data_writebuf[0], &data_readbuf[0], data_len)) { fprintf(stderr, "%lldth block, %lldth page, DATA verify error\n", (long long)mtd_block_num, (long long)mtd_page_num); verify_failed |= (DUMP_DATA_READBUF | DUMP_DATA_WRITEBUF); } mark_bad_and_skip: if (verify_failed) { if (mark) { /* mark this block to bad */ if (mtd_mark_badblock(info, mtd_block_num) < 0) goto out; /* jump to first page of next block */ fprintf(stderr, "jump mtd: %lld:%lld -> ", (long long)mtd_block_num, (long long)mtd_page_num); mtd_block_num ++; mtd_page_num = 0; fprintf(stderr, "%lld:%lld\n", (long long)mtd_block_num, (long long)mtd_page_num); if (mtd_block_num >= mtd_blocks) { printf("not enough space.\n"); goto out; } /* rollback file offset to first page of this block*/ fprintf(stderr, "rollback file: %lld->", (long long)img_offset); img_offset -= img_offset % (mtd_pages * page_len) ; fprintf(stderr, "%lld\n", (long long)img_offset); /* retry */ continue; } if (verify_failed & DUMP_DATA_READBUF) t_dump_data((char*)&data_readbuf[0], data_len); if (verify_failed & DUMP_DATA_WRITEBUF) t_dump_data((char*)&data_writebuf[0], data_len); if (verify_failed & DUMP_OOB_READBUF) t_dump_data((char*)&oob_readbuf[0], YAFFS2_TAG_SIZE); if (verify_failed & DUMP_OOB_WRITEBUF) t_dump_data((char*)&oob_writebuf[0], YAFFS2_TAG_SIZE); goto out; } next_page: /* goto next page on file */ img_offset += page_len; /* goto next page of this block */ mtd_page_num ++; if (mtd_page_num >= mtd_pages) { /* goto first page of next block */ mtd_block_num ++; mtd_page_num = 0; } } while (1); ret = EXIT_SUCCESS; out: printf("\n"); if (read_dlen < 0) { perror("read data area from file"); } if (read_olen < 0) { perror("read oob area from file"); } return ret; } int parse_opt(int argc, char *argv[]) { int error = 0; for (;;) { int option_index = 0; static const char *short_options = "fmden"; static const struct option long_options[] = { {"help", no_argument, 0, 0}, {"forceerase", no_argument, 0, 'f'}, {"markbad", no_argument, 0, 'm'}, {"donoterase", no_argument, 0, 'd'}, {"eraseonly", no_argument, 0, 'e'}, {"noverify", no_argument, 0, 'n'}, {0, 0, 0, 0}, }; int c = getopt_long(argc, argv, short_options, long_options, &option_index); if (c == EOF) { break; } switch (c) { case 0: return -1; case 'f': force_erase = 1; break; case 'm': mark_bad = 1; break; case 'd': do_not_erase = 1; break; case 'e': erase_only = 1; break; case 'n': do_not_verify = 1; break; case '?': error = 1; break; } } if (error) return -1; if ((argc - optind) < 1) return -1; mtd_device = argv[optind]; if (!erase_only && ((argc - optind) < 2)) return -1; optind++; img = argv[optind]; return 0; } void display_help (void) { printf("Usage: mkyaffs2 [OPTION] MTD_DEVICE [INPUTFILE]\n" "Writes to the specified MTD device.\n" "\n" // " -f, --forceerase enforce erase even if marked as badblock\n" " -m, --markbad mark as badblock when verify failed\n" " -d, --donoterase do not erase\n" " -e, --eraseonly do not write\n" " -n, --noverify do not verify after write\n" " --help display this help and exit\n"); } int main(int argc, char *argv[]) { struct mk_info info; struct mtd_info_user meminfo; mk_ooblayout ooblayout; struct stat file_st; int fd = -1; int img_fd = -1; int ret = EXIT_FAILURE; unsigned int page_len, data_len, spare_len; int i; memset(&ffchars, 0xff, sizeof(ffchars)); memset(&info, 0x00, sizeof(info)); info.mtd_fd = -1; info.img_fd = -1; if (parse_opt(argc, argv) < 0) { display_help(); goto out; } printf("consider yourself ...\n"); printf(" sizeof(off_t) = %d\n", sizeof(off_t)); printf(" sizeof(struct mtd_oob_buf) = %d\n", sizeof(struct mtd_oob_buf)); printf("\n"); /* Open the device */ if ((fd = open(mtd_device, O_RDWR)) == -1) { perror("open mtd"); goto out; } /* Fill in MTD device capability structure */ if (ioctl(fd, MEMGETINFO, &meminfo) != 0) { perror("MEMGETINFO"); goto out; } if (ioctl(fd, MK_MTD_GET_OOBLAYOUT, &ooblayout) < 0) { perror("MEMGETOOBSEL or ECCGETLAYOUT"); goto out; } data_len = meminfo.writesize; spare_len = meminfo.oobsize; page_len = data_len + spare_len; printf("[%s]\n", mtd_device); printf(" type : 0x%08x\n", meminfo.type); printf(" flags : 0x%08x\n", meminfo.flags); printf(" size : %d bytes (0x%08x)\n", meminfo.size, meminfo.size); printf("\n"); printf(" DATA length : %6d bytes\n", data_len); printf(" OOB length : %6d bytes\n", spare_len); printf(" PAGE length : %6d bytes\n", page_len); printf(" BLOCK length : %6d bytes\n", meminfo.erasesize); printf("\n"); printf("pages per block: %6d ea\n", meminfo.erasesize / data_len); printf(" total blocks : %6d ea\n", meminfo.size / meminfo.erasesize); printf(" total pages : %6d ea\n", meminfo.size / data_len); printf("\n"); info.ooblayout = &ooblayout; info.mtdinfo = &meminfo; info.mtd_fd = fd; if (erase_only) goto skip_file_ops; if (!(spare_len == 64 && data_len == 2048)) { fprintf(stderr, "YAFFS2 does not support this chip.\n"); goto out; } img_fd = open(img, O_RDONLY); if (img_fd < 0) { perror("open file"); goto out; } if (fstat(img_fd, &file_st) < 0) { perror("stat file"); goto out; } if (file_st.st_size % page_len) { fprintf(stderr, "file size:%ld, not page aligned\n", (long)file_st.st_size); goto out; } printf("[%s]\n", img); printf(" size : %9d bytes (0x%08x)\n", (int)file_st.st_size, (int)file_st.st_size); printf(" data area : %9d bytes (0x%08x)\n", (int)file_st.st_size / page_len * data_len, (int)file_st.st_size / page_len * data_len); printf(" oob area : %9d bytes (0x%08x)\n", (int)file_st.st_size / page_len * spare_len, (int)file_st.st_size / page_len * spare_len); printf("\n"); if (meminfo.size < file_st.st_size / page_len * data_len) { fprintf(stderr, "file is larger than device space\n"); goto out; } info.img_fd = img_fd; info.img_size = file_st.st_size; info.mtd_oob_avail = 0; #ifdef ECCGETLAYOUT for (i = 0; i < MTD_MAX_OOBFREE_ENTRIES; i++) { if (!ooblayout.oobfree[i].length) break; info.mtd_oob_avail += ooblayout.oobfree[i].length; } if (info.mtd_oob_avail != ooblayout.oobavail) { fprintf(stderr, "confused. what is the real oofavail ? %d or %d ?\n", info.mtd_oob_avail, ooblayout.oobavail); // goto out; } #else if (ooblayout.useecc == MTD_NANDECC_AUTOPLACE) { for (i = 0; ooblayout.oobfree[i][1]; i++) { info.mtd_oob_avail += ooblayout.oobfree[i][1]; } } #endif if (info.mtd_oob_avail < YAFFS2_TAG_SIZE) { fprintf(stderr, "free oob space of driver's layout : %d bytes\n", info.mtd_oob_avail); fprintf(stderr, "but not enough, (%d < %d)\n", info.mtd_oob_avail, YAFFS2_TAG_SIZE); goto out; } skip_file_ops: bbt_buf = (int*)malloc(meminfo.size * sizeof(int) / meminfo.erasesize); if (!bbt_buf) { perror("bbt_buf"); goto out; } reset_bbt(bbt_buf); printf("\n"); printf("scanning badblock...\n"); ret = scan_bbt(&info, bbt_buf); if (ret < 0) goto out; printf(" %d bad blocks\n", ret); if (!do_not_erase) { printf("\n"); printf("erasing whole of device...\n"); ret = erase_all(&info, force_erase); if (ret < 0) goto out; /* for sync, needed ? */ close(fd); fd = -1; info.mtd_fd = -1; if ((fd = open(mtd_device, O_RDWR)) == -1) { perror("open mtd"); goto out; } info.mtd_fd = fd; if (force_erase) { printf("\n"); printf("re-scanning badblock...\n"); ret = scan_bbt(&info, bbt_buf); if (ret < 0) goto out; printf(" %d bad blocks\n", ret); } } if (!erase_only) { printf("\n"); printf("writing...\n"); ret = process_file(&info, mark_bad, do_not_verify); if (ret == EXIT_SUCCESS) { printf("successed\n"); } else { printf("failed\n"); } } out: if (fd >= 0) close(fd); fd = -1; if (img_fd >= 0) close(img_fd); img_fd = -1; if (bbt_buf) free(bbt_buf); bbt_buf = NULL; return ret; } /* * vim:ts=4 */ /* 2.6.16 * /opt/host/arm-2006q3/bin/arm-none-linux-gnueabi-gcc -I /work/mDiracIII/kernel-2.6/include -Wall -O2 -march=armv6 -mtune=arm1136j-s -s -o mkyaffs2 mkyaffs2.c */ /* 2.6.18 * /opt/host/arm-2006q3/bin/arm-none-linux-gnueabi-gcc -I /work/MIZI/linux-2.6.18-rc5-git2/include -Wall -O2 -march=armv6 -mtune=arm1136j-s -s -o mkyaffs2 mkyaffs2.c */