MTD: tests: add mtd_pagetest
Artem Bityutskiy [Mon, 8 Dec 2008 11:34:16 +0000 (13:34 +0200)]
This test checks that NAND pages read/write work fine.

Signed-off-by: Artem Bityutskiy <Artem.Bityutskiy@nokia.com>

drivers/mtd/tests/mtd_pagetest.c [new file with mode: 0644]

diff --git a/drivers/mtd/tests/mtd_pagetest.c b/drivers/mtd/tests/mtd_pagetest.c
new file mode 100644 (file)
index 0000000..9648818
--- /dev/null
@@ -0,0 +1,632 @@
+/*
+ * Copyright (C) 2006-2008 Nokia Corporation
+ *
+ * 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.
+ *
+ * Test page read and write on MTD device.
+ *
+ * Author: Adrian Hunter <ext-adrian.hunter@nokia.com>
+ */
+
+#include <asm/div64.h>
+#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_pagetest: "
+
+static int dev;
+module_param(dev, int, S_IRUGO);
+MODULE_PARM_DESC(dev, "MTD device number to use");
+
+static struct mtd_info *mtd;
+static unsigned char *twopages;
+static unsigned char *writebuf;
+static unsigned char *boundary;
+static unsigned char *bbt;
+
+static int pgsize;
+static int bufsize;
+static int ebcnt;
+static int pgcnt;
+static int errcnt;
+static unsigned long next = 1;
+
+static inline unsigned int simple_rand(void)
+{
+       next = next * 1103515245 + 12345;
+       return (unsigned int)((next / 65536) % 32768);
+}
+
+static inline void simple_srand(unsigned long seed)
+{
+       next = seed;
+}
+
+static void set_random_data(unsigned char *buf, size_t len)
+{
+       size_t i;
+
+       for (i = 0; i < len; ++i)
+               buf[i] = simple_rand();
+}
+
+static 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;
+}
+
+static int write_eraseblock(int ebnum)
+{
+       int err = 0;
+       size_t written = 0;
+       loff_t addr = ebnum * mtd->erasesize;
+
+       set_random_data(writebuf, mtd->erasesize);
+       cond_resched();
+       err = mtd->write(mtd, addr, mtd->erasesize, &written, writebuf);
+       if (err || written != mtd->erasesize)
+               printk(PRINT_PREF "error: write failed at %#llx\n",
+                      (long long)addr);
+
+       return err;
+}
+
+static int verify_eraseblock(int ebnum)
+{
+       uint32_t j;
+       size_t read = 0;
+       int err = 0, i;
+       loff_t addr0, addrn;
+       loff_t addr = ebnum * mtd->erasesize;
+
+       addr0 = 0;
+       for (i = 0; bbt[i] && i < ebcnt; ++i)
+               addr0 += mtd->erasesize;
+
+       addrn = mtd->size;
+       for (i = 0; bbt[ebcnt - i - 1] && i < ebcnt; ++i)
+               addrn -= mtd->erasesize;
+
+       set_random_data(writebuf, mtd->erasesize);
+       for (j = 0; j < pgcnt - 1; ++j, addr += pgsize) {
+               /* Do a read to set the internal dataRAMs to different data */
+               err = mtd->read(mtd, addr0, bufsize, &read, twopages);
+               if (err == -EUCLEAN)
+                       err = 0;
+               if (err || read != bufsize) {
+                       printk(PRINT_PREF "error: read failed at %#llx\n",
+                              (long long)addr0);
+                       return err;
+               }
+               err = mtd->read(mtd, addrn - bufsize, bufsize, &read, twopages);
+               if (err == -EUCLEAN)
+                       err = 0;
+               if (err || read != bufsize) {
+                       printk(PRINT_PREF "error: read failed at %#llx\n",
+                              (long long)(addrn - bufsize));
+                       return err;
+               }
+               memset(twopages, 0, bufsize);
+               read = 0;
+               err = mtd->read(mtd, addr, bufsize, &read, twopages);
+               if (err == -EUCLEAN)
+                       err = 0;
+               if (err || read != bufsize) {
+                       printk(PRINT_PREF "error: read failed at %#llx\n",
+                              (long long)addr);
+                       break;
+               }
+               if (memcmp(twopages, writebuf + (j * pgsize), bufsize)) {
+                       printk(PRINT_PREF "error: verify failed at %#llx\n",
+                              (long long)addr);
+                       errcnt += 1;
+               }
+       }
+       /* Check boundary between eraseblocks */
+       if (addr <= addrn - pgsize - pgsize && !bbt[ebnum + 1]) {
+               unsigned long oldnext = next;
+               /* Do a read to set the internal dataRAMs to different data */
+               err = mtd->read(mtd, addr0, bufsize, &read, twopages);
+               if (err == -EUCLEAN)
+                       err = 0;
+               if (err || read != bufsize) {
+                       printk(PRINT_PREF "error: read failed at %#llx\n",
+                              (long long)addr0);
+                       return err;
+               }
+               err = mtd->read(mtd, addrn - bufsize, bufsize, &read, twopages);
+               if (err == -EUCLEAN)
+                       err = 0;
+               if (err || read != bufsize) {
+                       printk(PRINT_PREF "error: read failed at %#llx\n",
+                              (long long)(addrn - bufsize));
+                       return err;
+               }
+               memset(twopages, 0, bufsize);
+               read = 0;
+               err = mtd->read(mtd, addr, bufsize, &read, twopages);
+               if (err == -EUCLEAN)
+                       err = 0;
+               if (err || read != bufsize) {
+                       printk(PRINT_PREF "error: read failed at %#llx\n",
+                              (long long)addr);
+                       return err;
+               }
+               memcpy(boundary, writebuf + mtd->erasesize - pgsize, pgsize);
+               set_random_data(boundary + pgsize, pgsize);
+               if (memcmp(twopages, boundary, bufsize)) {
+                       printk(PRINT_PREF "error: verify failed at %#llx\n",
+                              (long long)addr);
+                       errcnt += 1;
+               }
+               next = oldnext;
+       }
+       return err;
+}
+
+static int crosstest(void)
+{
+       size_t read = 0;
+       int err = 0, i;
+       loff_t addr, addr0, addrn;
+       unsigned char *pp1, *pp2, *pp3, *pp4;
+
+       printk(PRINT_PREF "crosstest\n");
+       pp1 = kmalloc(pgsize * 4, GFP_KERNEL);
+       if (!pp1) {
+               printk(PRINT_PREF "error: cannot allocate memory\n");
+               return -ENOMEM;
+       }
+       pp2 = pp1 + pgsize;
+       pp3 = pp2 + pgsize;
+       pp4 = pp3 + pgsize;
+       memset(pp1, 0, pgsize * 4);
+
+       addr0 = 0;
+       for (i = 0; bbt[i] && i < ebcnt; ++i)
+               addr0 += mtd->erasesize;
+
+       addrn = mtd->size;
+       for (i = 0; bbt[ebcnt - i - 1] && i < ebcnt; ++i)
+               addrn -= mtd->erasesize;
+
+       /* Read 2nd-to-last page to pp1 */
+       read = 0;
+       addr = addrn - pgsize - pgsize;
+       err = mtd->read(mtd, addr, pgsize, &read, pp1);
+       if (err == -EUCLEAN)
+               err = 0;
+       if (err || read != pgsize) {
+               printk(PRINT_PREF "error: read failed at %#llx\n",
+                      (long long)addr);
+               kfree(pp1);
+               return err;
+       }
+
+       /* Read 3rd-to-last page to pp1 */
+       read = 0;
+       addr = addrn - pgsize - pgsize - pgsize;
+       err = mtd->read(mtd, addr, pgsize, &read, pp1);
+       if (err == -EUCLEAN)
+               err = 0;
+       if (err || read != pgsize) {
+               printk(PRINT_PREF "error: read failed at %#llx\n",
+                      (long long)addr);
+               kfree(pp1);
+               return err;
+       }
+
+       /* Read first page to pp2 */
+       read = 0;
+       addr = addr0;
+       printk(PRINT_PREF "reading page at %#llx\n", (long long)addr);
+       err = mtd->read(mtd, addr, pgsize, &read, pp2);
+       if (err == -EUCLEAN)
+               err = 0;
+       if (err || read != pgsize) {
+               printk(PRINT_PREF "error: read failed at %#llx\n",
+                      (long long)addr);
+               kfree(pp1);
+               return err;
+       }
+
+       /* Read last page to pp3 */
+       read = 0;
+       addr = addrn - pgsize;
+       printk(PRINT_PREF "reading page at %#llx\n", (long long)addr);
+       err = mtd->read(mtd, addr, pgsize, &read, pp3);
+       if (err == -EUCLEAN)
+               err = 0;
+       if (err || read != pgsize) {
+               printk(PRINT_PREF "error: read failed at %#llx\n",
+                      (long long)addr);
+               kfree(pp1);
+               return err;
+       }
+
+       /* Read first page again to pp4 */
+       read = 0;
+       addr = addr0;
+       printk(PRINT_PREF "reading page at %#llx\n", (long long)addr);
+       err = mtd->read(mtd, addr, pgsize, &read, pp4);
+       if (err == -EUCLEAN)
+               err = 0;
+       if (err || read != pgsize) {
+               printk(PRINT_PREF "error: read failed at %#llx\n",
+                      (long long)addr);
+               kfree(pp1);
+               return err;
+       }
+
+       /* pp2 and pp4 should be the same */
+       printk(PRINT_PREF "verifying pages read at %#llx match\n",
+              (long long)addr0);
+       if (memcmp(pp2, pp4, pgsize)) {
+               printk(PRINT_PREF "verify failed!\n");
+               errcnt += 1;
+       } else if (!err)
+               printk(PRINT_PREF "crosstest ok\n");
+       kfree(pp1);
+       return err;
+}
+
+static int erasecrosstest(void)
+{
+       size_t read = 0, written = 0;
+       int err = 0, i, ebnum, ok = 1, ebnum2;
+       loff_t addr0;
+       char *readbuf = twopages;
+
+       printk(PRINT_PREF "erasecrosstest\n");
+
+       ebnum = 0;
+       addr0 = 0;
+       for (i = 0; bbt[i] && i < ebcnt; ++i) {
+               addr0 += mtd->erasesize;
+               ebnum += 1;
+       }
+
+       ebnum2 = ebcnt - 1;
+       while (ebnum2 && bbt[ebnum2])
+               ebnum2 -= 1;
+
+       printk(PRINT_PREF "erasing block %d\n", ebnum);
+       err = erase_eraseblock(ebnum);
+       if (err)
+               return err;
+
+       printk(PRINT_PREF "writing 1st page of block %d\n", ebnum);
+       set_random_data(writebuf, pgsize);
+       strcpy(writebuf, "There is no data like this!");
+       err = mtd->write(mtd, addr0, pgsize, &written, writebuf);
+       if (err || written != pgsize) {
+               printk(PRINT_PREF "error: write failed at %#llx\n",
+                      (long long)addr0);
+               return err ? err : -1;
+       }
+
+       printk(PRINT_PREF "reading 1st page of block %d\n", ebnum);
+       memset(readbuf, 0, pgsize);
+       err = mtd->read(mtd, addr0, pgsize, &read, readbuf);
+       if (err == -EUCLEAN)
+               err = 0;
+       if (err || read != pgsize) {
+               printk(PRINT_PREF "error: read failed at %#llx\n",
+                      (long long)addr0);
+               return err ? err : -1;
+       }
+
+       printk(PRINT_PREF "verifying 1st page of block %d\n", ebnum);
+       if (memcmp(writebuf, readbuf, pgsize)) {
+               printk(PRINT_PREF "verify failed!\n");
+               errcnt += 1;
+               ok = 0;
+               return err;
+       }
+
+       printk(PRINT_PREF "erasing block %d\n", ebnum);
+       err = erase_eraseblock(ebnum);
+       if (err)
+               return err;
+
+       printk(PRINT_PREF "writing 1st page of block %d\n", ebnum);
+       set_random_data(writebuf, pgsize);
+       strcpy(writebuf, "There is no data like this!");
+       err = mtd->write(mtd, addr0, pgsize, &written, writebuf);
+       if (err || written != pgsize) {
+               printk(PRINT_PREF "error: write failed at %#llx\n",
+                      (long long)addr0);
+               return err ? err : -1;
+       }
+
+       printk(PRINT_PREF "erasing block %d\n", ebnum2);
+       err = erase_eraseblock(ebnum2);
+       if (err)
+               return err;
+
+       printk(PRINT_PREF "reading 1st page of block %d\n", ebnum);
+       memset(readbuf, 0, pgsize);
+       err = mtd->read(mtd, addr0, pgsize, &read, readbuf);
+       if (err == -EUCLEAN)
+               err = 0;
+       if (err || read != pgsize) {
+               printk(PRINT_PREF "error: read failed at %#llx\n",
+                      (long long)addr0);
+               return err ? err : -1;
+       }
+
+       printk(PRINT_PREF "verifying 1st page of block %d\n", ebnum);
+       if (memcmp(writebuf, readbuf, pgsize)) {
+               printk(PRINT_PREF "verify failed!\n");
+               errcnt += 1;
+               ok = 0;
+       }
+
+       if (ok && !err)
+               printk(PRINT_PREF "erasecrosstest ok\n");
+       return err;
+}
+
+static int erasetest(void)
+{
+       size_t read = 0, written = 0;
+       int err = 0, i, ebnum, ok = 1;
+       loff_t addr0;
+
+       printk(PRINT_PREF "erasetest\n");
+
+       ebnum = 0;
+       addr0 = 0;
+       for (i = 0; bbt[i] && i < ebcnt; ++i) {
+               addr0 += mtd->erasesize;
+               ebnum += 1;
+       }
+
+       printk(PRINT_PREF "erasing block %d\n", ebnum);
+       err = erase_eraseblock(ebnum);
+       if (err)
+               return err;
+
+       printk(PRINT_PREF "writing 1st page of block %d\n", ebnum);
+       set_random_data(writebuf, pgsize);
+       err = mtd->write(mtd, addr0, pgsize, &written, writebuf);
+       if (err || written != pgsize) {
+               printk(PRINT_PREF "error: write failed at %#llx\n",
+                      (long long)addr0);
+               return err ? err : -1;
+       }
+
+       printk(PRINT_PREF "erasing block %d\n", ebnum);
+       err = erase_eraseblock(ebnum);
+       if (err)
+               return err;
+
+       printk(PRINT_PREF "reading 1st page of block %d\n", ebnum);
+       err = mtd->read(mtd, addr0, pgsize, &read, twopages);
+       if (err == -EUCLEAN)
+               err = 0;
+       if (err || read != pgsize) {
+               printk(PRINT_PREF "error: read failed at %#llx\n",
+                      (long long)addr0);
+               return err ? err : -1;
+       }
+
+       printk(PRINT_PREF "verifying 1st page of block %d is all 0xff\n",
+              ebnum);
+       for (i = 0; i < pgsize; ++i)
+               if (twopages[i] != 0xff) {
+                       printk(PRINT_PREF "verifying all 0xff failed at %d\n",
+                              i);
+                       errcnt += 1;
+                       ok = 0;
+                       break;
+               }
+
+       if (ok && !err)
+               printk(PRINT_PREF "erasetest ok\n");
+
+       return err;
+}
+
+static int is_block_bad(int ebnum)
+{
+       loff_t addr = ebnum * mtd->erasesize;
+       int ret;
+
+       ret = mtd->block_isbad(mtd, addr);
+       if (ret)
+               printk(PRINT_PREF "block %d is bad\n", ebnum);
+       return ret;
+}
+
+static int scan_for_bad_eraseblocks(void)
+{
+       int i, bad = 0;
+
+       bbt = kmalloc(ebcnt, GFP_KERNEL);
+       if (!bbt) {
+               printk(PRINT_PREF "error: cannot allocate memory\n");
+               return -ENOMEM;
+       }
+       memset(bbt, 0 , ebcnt);
+
+       printk(PRINT_PREF "scanning for bad eraseblocks\n");
+       for (i = 0; i < ebcnt; ++i) {
+               bbt[i] = is_block_bad(i) ? 1 : 0;
+               if (bbt[i])
+                       bad += 1;
+               cond_resched();
+       }
+       printk(PRINT_PREF "scanned %d eraseblocks, %d are bad\n", i, bad);
+       return 0;
+}
+
+static int __init mtd_pagetest_init(void)
+{
+       int err = 0;
+       uint64_t tmp;
+       uint32_t i;
+
+       printk(KERN_INFO "\n");
+       printk(KERN_INFO "=================================================\n");
+       printk(PRINT_PREF "MTD device: %d\n", dev);
+
+       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->type != MTD_NANDFLASH) {
+               printk(PRINT_PREF "this test requires NAND flash\n");
+               goto out;
+       }
+
+       tmp = mtd->size;
+       do_div(tmp, mtd->erasesize);
+       ebcnt = tmp;
+       pgcnt = mtd->erasesize / mtd->writesize;
+
+       printk(PRINT_PREF "MTD device size %llu, eraseblock size %u, "
+              "page size %u, count of eraseblocks %u, pages per "
+              "eraseblock %u, OOB size %u\n",
+              (unsigned long long)mtd->size, mtd->erasesize,
+              pgsize, ebcnt, pgcnt, mtd->oobsize);
+
+       err = -ENOMEM;
+       bufsize = pgsize * 2;
+       writebuf = kmalloc(mtd->erasesize, GFP_KERNEL);
+       if (!writebuf) {
+               printk(PRINT_PREF "error: cannot allocate memory\n");
+               goto out;
+       }
+       twopages = kmalloc(bufsize, GFP_KERNEL);
+       if (!twopages) {
+               printk(PRINT_PREF "error: cannot allocate memory\n");
+               goto out;
+       }
+       boundary = kmalloc(bufsize, GFP_KERNEL);
+       if (!boundary) {
+               printk(PRINT_PREF "error: cannot allocate memory\n");
+               goto out;
+       }
+
+       err = scan_for_bad_eraseblocks();
+       if (err)
+               goto out;
+
+       /* Erase all eraseblocks */
+       printk(PRINT_PREF "erasing whole device\n");
+       for (i = 0; i < ebcnt; ++i) {
+               if (bbt[i])
+                       continue;
+               err = erase_eraseblock(i);
+               if (err)
+                       goto out;
+               cond_resched();
+       }
+       printk(PRINT_PREF "erased %u eraseblocks\n", i);
+
+       /* Write all eraseblocks */
+       simple_srand(1);
+       printk(PRINT_PREF "writing whole device\n");
+       for (i = 0; i < ebcnt; ++i) {
+               if (bbt[i])
+                       continue;
+               err = write_eraseblock(i);
+               if (err)
+                       goto out;
+               if (i % 256 == 0)
+                       printk(PRINT_PREF "written up to eraseblock %u\n", i);
+               cond_resched();
+       }
+       printk(PRINT_PREF "written %u eraseblocks\n", i);
+
+       /* Check all eraseblocks */
+       simple_srand(1);
+       printk(PRINT_PREF "verifying all eraseblocks\n");
+       for (i = 0; i < ebcnt; ++i) {
+               if (bbt[i])
+                       continue;
+               err = verify_eraseblock(i);
+               if (err)
+                       goto out;
+               if (i % 256 == 0)
+                       printk(PRINT_PREF "verified up to eraseblock %u\n", i);
+               cond_resched();
+       }
+       printk(PRINT_PREF "verified %u eraseblocks\n", i);
+
+       err = crosstest();
+       if (err)
+               goto out;
+
+       err = erasecrosstest();
+       if (err)
+               goto out;
+
+       err = erasetest();
+       if (err)
+               goto out;
+
+       printk(PRINT_PREF "finished with %d errors\n", errcnt);
+out:
+
+       kfree(bbt);
+       kfree(boundary);
+       kfree(twopages);
+       kfree(writebuf);
+       put_mtd_device(mtd);
+       if (err)
+               printk(PRINT_PREF "error %d occurred\n", err);
+       printk(KERN_INFO "=================================================\n");
+       return err;
+}
+module_init(mtd_pagetest_init);
+
+static void __exit mtd_pagetest_exit(void)
+{
+       return;
+}
+module_exit(mtd_pagetest_exit);
+
+MODULE_DESCRIPTION("NAND page test");
+MODULE_AUTHOR("Adrian Hunter");
+MODULE_LICENSE("GPL");