diff options
author | Artem Bityutskiy <Artem.Bityutskiy@nokia.com> | 2008-12-08 13:38:34 +0200 |
---|---|---|
committer | Artem Bityutskiy <Artem.Bityutskiy@nokia.com> | 2008-12-10 17:00:40 +0200 |
commit | 4db451a7649decfdcb29d21016d7e031accc5cda (patch) | |
tree | e09806c2dd71a4ec3146236e217291a6f534ed4b | |
parent | bf60862a58f7cd881cfe86a3b2aceaea4a22b3b0 (diff) |
MTD: tests: add mtd_torturetest
This test is designed to work for very long time and it tries to
wear few eraseblocks.
Signed-off-by: Artem Bityutskiy <Artem.Bityutskiy@nokia.com>
-rw-r--r-- | drivers/mtd/tests/mtd_torturetest.c | 530 |
1 files changed, 530 insertions, 0 deletions
diff --git a/drivers/mtd/tests/mtd_torturetest.c b/drivers/mtd/tests/mtd_torturetest.c new file mode 100644 index 00000000000..2928d9e84ea --- /dev/null +++ b/drivers/mtd/tests/mtd_torturetest.c @@ -0,0 +1,530 @@ +/* + * Copyright (C) 2006-2008 Artem Bityutskiy + * Copyright (C) 2006-2008 Jarkko Lavinen + * Copyright (C) 2006-2008 Adrian Hunter + * + * 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. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; see the file COPYING. If not, write to the Free Software + * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * Authors: Artem Bityutskiy, Jarkko Lavinen, Adria Hunter + * + * WARNING: this test program may kill your flash and your device. Do not + * use it unless you know what you do. Authors are not responsible for any + * damage caused by this program. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/err.h> +#include <linux/mtd/mtd.h> +#include <linux/sched.h> + +#define PRINT_PREF KERN_INFO "mtd_torturetest: " +#define RETRIES 3 + +static int eb = 8; +module_param(eb, int, S_IRUGO); +MODULE_PARM_DESC(eb, "eraseblock number within the selected MTD device"); + +static int ebcnt = 32; +module_param(ebcnt, int, S_IRUGO); +MODULE_PARM_DESC(ebcnt, "number of consecutive eraseblocks to torture"); + +static int pgcnt; +module_param(pgcnt, int, S_IRUGO); +MODULE_PARM_DESC(pgcnt, "number of pages per eraseblock to torture (0 => all)"); + +static int dev; +module_param(dev, int, S_IRUGO); +MODULE_PARM_DESC(dev, "MTD device number to use"); + +static int gran = 512; +module_param(gran, int, S_IRUGO); +MODULE_PARM_DESC(gran, "how often the status information should be printed"); + +static int check = 1; +module_param(check, int, S_IRUGO); +MODULE_PARM_DESC(check, "if the written data should be checked"); + +static unsigned int cycles_count; +module_param(cycles_count, uint, S_IRUGO); +MODULE_PARM_DESC(cycles_count, "how many erase cycles to do " + "(infinite by default)"); + +static struct mtd_info *mtd; + +/* This buffer contains 0x555555...0xAAAAAA... pattern */ +static unsigned char *patt_5A5; +/* This buffer contains 0xAAAAAA...0x555555... pattern */ +static unsigned char *patt_A5A; +/* This buffer contains all 0xFF bytes */ +static unsigned char *patt_FF; +/* This a temporary buffer is use when checking data */ +static unsigned char *check_buf; +/* How many erase cycles were done */ +static unsigned int erase_cycles; + +static int pgsize; +static struct timeval start, finish; + +static void report_corrupt(unsigned char *read, unsigned char *written); + +static inline void start_timing(void) +{ + do_gettimeofday(&start); +} + +static inline void stop_timing(void) +{ + do_gettimeofday(&finish); +} + +/* + * Erase eraseblock number @ebnum. + */ +static inline int erase_eraseblock(int ebnum) +{ + int err; + struct erase_info ei; + loff_t addr = ebnum * mtd->erasesize; + + memset(&ei, 0, sizeof(struct erase_info)); + ei.mtd = mtd; + ei.addr = addr; + ei.len = mtd->erasesize; + + err = mtd->erase(mtd, &ei); + if (err) { + printk(PRINT_PREF "error %d while erasing EB %d\n", err, ebnum); + return err; + } + + if (ei.state == MTD_ERASE_FAILED) { + printk(PRINT_PREF "some erase error occurred at EB %d\n", + ebnum); + return -EIO; + } + + return 0; +} + +/* + * Check that the contents of eraseblock number @enbum is equivalent to the + * @buf buffer. + */ +static inline int check_eraseblock(int ebnum, unsigned char *buf) +{ + int err, retries = 0; + size_t read = 0; + loff_t addr = ebnum * mtd->erasesize; + size_t len = mtd->erasesize; + + if (pgcnt) { + addr = (ebnum + 1) * mtd->erasesize - pgcnt * pgsize; + len = pgcnt * pgsize; + } + +retry: + err = mtd->read(mtd, addr, len, &read, check_buf); + if (err == -EUCLEAN) + printk(PRINT_PREF "single bit flip occurred at EB %d " + "MTD reported that it was fixed.\n", ebnum); + else if (err) { + printk(PRINT_PREF "error %d while reading EB %d, " + "read %zd\n", err, ebnum, read); + return err; + } + + if (read != len) { + printk(PRINT_PREF "failed to read %zd bytes from EB %d, " + "read only %zd, but no error reported\n", + len, ebnum, read); + return -EIO; + } + + if (memcmp(buf, check_buf, len)) { + printk(PRINT_PREF "read wrong data from EB %d\n", ebnum); + report_corrupt(check_buf, buf); + + if (retries++ < RETRIES) { + /* Try read again */ + yield(); + printk(PRINT_PREF "re-try reading data from EB %d\n", + ebnum); + goto retry; + } else { + printk(PRINT_PREF "retried %d times, still errors, " + "give-up\n", RETRIES); + return -EINVAL; + } + } + + if (retries != 0) + printk(PRINT_PREF "only attempt number %d was OK (!!!)\n", + retries); + + return 0; +} + +static inline int write_pattern(int ebnum, void *buf) +{ + int err; + size_t written = 0; + loff_t addr = ebnum * mtd->erasesize; + size_t len = mtd->erasesize; + + if (pgcnt) { + addr = (ebnum + 1) * mtd->erasesize - pgcnt * pgsize; + len = pgcnt * pgsize; + } + err = mtd->write(mtd, addr, len, &written, buf); + if (err) { + printk(PRINT_PREF "error %d while writing EB %d, written %zd" + " bytes\n", err, ebnum, written); + return err; + } + if (written != len) { + printk(PRINT_PREF "written only %zd bytes of %zd, but no error" + " reported\n", written, len); + return -EIO; + } + + return 0; +} + +static int __init tort_init(void) +{ + int err = 0, i, infinite = !cycles_count; + int bad_ebs[ebcnt]; + + printk(KERN_INFO "\n"); + printk(KERN_INFO "=================================================\n"); + printk(PRINT_PREF "Warning: this program is trying to wear out your " + "flash, stop it if this is not wanted.\n"); + printk(PRINT_PREF "MTD device: %d\n", dev); + printk(PRINT_PREF "torture %d eraseblocks (%d-%d) of mtd%d\n", + ebcnt, eb, eb + ebcnt - 1, dev); + if (pgcnt) + printk(PRINT_PREF "torturing just %d pages per eraseblock\n", + pgcnt); + printk(PRINT_PREF "write verify %s\n", check ? "enabled" : "disabled"); + + mtd = get_mtd_device(NULL, dev); + if (IS_ERR(mtd)) { + err = PTR_ERR(mtd); + printk(PRINT_PREF "error: cannot get MTD device\n"); + return err; + } + + if (mtd->writesize == 1) { + printk(PRINT_PREF "not NAND flash, assume page size is 512 " + "bytes.\n"); + pgsize = 512; + } else + pgsize = mtd->writesize; + + if (pgcnt && (pgcnt > mtd->erasesize / pgsize || pgcnt < 0)) { + printk(PRINT_PREF "error: invalid pgcnt value %d\n", pgcnt); + goto out_mtd; + } + + err = -ENOMEM; + patt_5A5 = kmalloc(mtd->erasesize, GFP_KERNEL); + if (!patt_5A5) { + printk(PRINT_PREF "error: cannot allocate memory\n"); + goto out_mtd; + } + + patt_A5A = kmalloc(mtd->erasesize, GFP_KERNEL); + if (!patt_A5A) { + printk(PRINT_PREF "error: cannot allocate memory\n"); + goto out_patt_5A5; + } + + patt_FF = kmalloc(mtd->erasesize, GFP_KERNEL); + if (!patt_FF) { + printk(PRINT_PREF "error: cannot allocate memory\n"); + goto out_patt_A5A; + } + + check_buf = kmalloc(mtd->erasesize, GFP_KERNEL); + if (!check_buf) { + printk(PRINT_PREF "error: cannot allocate memory\n"); + goto out_patt_FF; + } + + err = 0; + + /* Initialize patterns */ + memset(patt_FF, 0xFF, mtd->erasesize); + for (i = 0; i < mtd->erasesize / pgsize; i++) { + if (!(i & 1)) { + memset(patt_5A5 + i * pgsize, 0x55, pgsize); + memset(patt_A5A + i * pgsize, 0xAA, pgsize); + } else { + memset(patt_5A5 + i * pgsize, 0xAA, pgsize); + memset(patt_A5A + i * pgsize, 0x55, pgsize); + } + } + + /* + * Check if there is a bad eraseblock among those we are going to test. + */ + memset(&bad_ebs[0], 0, sizeof(int) * ebcnt); + if (mtd->block_isbad) { + for (i = eb; i < eb + ebcnt; i++) { + err = mtd->block_isbad(mtd, + (loff_t)i * mtd->erasesize); + + if (err < 0) { + printk(PRINT_PREF "block_isbad() returned %d " + "for EB %d\n", err, i); + goto out; + } + + if (err) { + printk("EB %d is bad. Skip it.\n", i); + bad_ebs[i - eb] = 1; + } + } + } + + start_timing(); + while (1) { + int i; + void *patt; + + /* Erase all eraseblocks */ + for (i = eb; i < eb + ebcnt; i++) { + if (bad_ebs[i - eb]) + continue; + err = erase_eraseblock(i); + if (err) + goto out; + cond_resched(); + } + + /* Check if the eraseblocks contain only 0xFF bytes */ + if (check) { + for (i = eb; i < eb + ebcnt; i++) { + if (bad_ebs[i - eb]) + continue; + err = check_eraseblock(i, patt_FF); + if (err) { + printk(PRINT_PREF "verify failed" + " for 0xFF... pattern\n"); + goto out; + } + cond_resched(); + } + } + + /* Write the pattern */ + for (i = eb; i < eb + ebcnt; i++) { + if (bad_ebs[i - eb]) + continue; + if ((eb + erase_cycles) & 1) + patt = patt_5A5; + else + patt = patt_A5A; + err = write_pattern(i, patt); + if (err) + goto out; + cond_resched(); + } + + /* Verify what we wrote */ + if (check) { + for (i = eb; i < eb + ebcnt; i++) { + if (bad_ebs[i - eb]) + continue; + if ((eb + erase_cycles) & 1) + patt = patt_5A5; + else + patt = patt_A5A; + err = check_eraseblock(i, patt); + if (err) { + printk(PRINT_PREF "verify failed for %s" + " pattern\n", + ((eb + erase_cycles) & 1) ? + "0x55AA55..." : "0xAA55AA..."); + goto out; + } + cond_resched(); + } + } + + erase_cycles += 1; + + if (erase_cycles % gran == 0) { + long ms; + + stop_timing(); + ms = (finish.tv_sec - start.tv_sec) * 1000 + + (finish.tv_usec - start.tv_usec) / 1000; + printk(PRINT_PREF "%08u erase cycles done, took %lu " + "milliseconds (%lu seconds)\n", + erase_cycles, ms, ms / 1000); + start_timing(); + } + + if (!infinite && --cycles_count == 0) + break; + } +out: + + printk(PRINT_PREF "finished after %u erase cycles\n", + erase_cycles); + kfree(check_buf); +out_patt_FF: + kfree(patt_FF); +out_patt_A5A: + kfree(patt_A5A); +out_patt_5A5: + kfree(patt_5A5); +out_mtd: + put_mtd_device(mtd); + if (err) + printk(PRINT_PREF "error %d occurred during torturing\n", err); + printk(KERN_INFO "=================================================\n"); + return err; +} +module_init(tort_init); + +static void __exit tort_exit(void) +{ + return; +} +module_exit(tort_exit); + +static int countdiffs(unsigned char *buf, unsigned char *check_buf, + unsigned offset, unsigned len, unsigned *bytesp, + unsigned *bitsp); +static void print_bufs(unsigned char *read, unsigned char *written, int start, + int len); + +/* + * Report the detailed information about how the read EB differs from what was + * written. + */ +static void report_corrupt(unsigned char *read, unsigned char *written) +{ + int i; + int bytes, bits, pages, first; + int offset, len; + size_t check_len = mtd->erasesize; + + if (pgcnt) + check_len = pgcnt * pgsize; + + bytes = bits = pages = 0; + for (i = 0; i < check_len; i += pgsize) + if (countdiffs(written, read, i, pgsize, &bytes, + &bits) >= 0) + pages++; + + printk(PRINT_PREF "verify fails on %d pages, %d bytes/%d bits\n", + pages, bytes, bits); + printk(PRINT_PREF "The following is a list of all differences between" + " what was read from flash and what was expected\n"); + + for (i = 0; i < check_len; i += pgsize) { + cond_resched(); + bytes = bits = 0; + first = countdiffs(written, read, i, pgsize, &bytes, + &bits); + if (first < 0) + continue; + + printk("-------------------------------------------------------" + "----------------------------------\n"); + + printk(PRINT_PREF "Page %d has %d bytes/%d bits failing verify," + " starting at offset 0x%x\n", + (mtd->erasesize - check_len + i) / pgsize, + bytes, bits, first); + + offset = first & ~0x7; + len = ((first + bytes) | 0x7) + 1 - offset; + + print_bufs(read, written, offset, len); + } +} + +static void print_bufs(unsigned char *read, unsigned char *written, int start, + int len) +{ + int i = 0, j1, j2; + char *diff; + + printk("Offset Read Written\n"); + while (i < len) { + printk("0x%08x: ", start + i); + diff = " "; + for (j1 = 0; j1 < 8 && i + j1 < len; j1++) { + printk(" %02x", read[start + i + j1]); + if (read[start + i + j1] != written[start + i + j1]) + diff = "***"; + } + + while (j1 < 8) { + printk(" "); + j1 += 1; + } + + printk(" %s ", diff); + + for (j2 = 0; j2 < 8 && i + j2 < len; j2++) + printk(" %02x", written[start + i + j2]); + printk("\n"); + i += 8; + } +} + +/* + * Count the number of differing bytes and bits and return the first differing + * offset. + */ +static int countdiffs(unsigned char *buf, unsigned char *check_buf, + unsigned offset, unsigned len, unsigned *bytesp, + unsigned *bitsp) +{ + unsigned i, bit; + int first = -1; + + for (i = offset; i < offset + len; i++) + if (buf[i] != check_buf[i]) { + first = i; + break; + } + + while (i < offset + len) { + if (buf[i] != check_buf[i]) { + (*bytesp)++; + bit = 1; + while (bit < 256) { + if ((buf[i] & bit) != (check_buf[i] & bit)) + (*bitsp)++; + bit <<= 1; + } + } + i++; + } + + return first; +} + +MODULE_DESCRIPTION("Eraseblock torturing module"); +MODULE_AUTHOR("Artem Bityutskiy, Jarkko Lavinen, Adrian Hunter"); +MODULE_LICENSE("GPL"); |