diff options
author | Adrian Hunter <ext-adrian.hunter@nokia.com> | 2007-03-19 06:47:45 -0400 |
---|---|---|
committer | David Woodhouse <dwmw2@infradead.org> | 2007-04-17 13:55:08 -0400 |
commit | 514087e74fb401a6621e8c836f4eaab87c269f24 (patch) | |
tree | a963d3669d42d0b8d3872a53ce511049c4a7363c /drivers/mtd | |
parent | 2b77a0ed54eeea61937e7f71b0487b815edfbcdf (diff) |
[MTD] nandsim: enhance nandsim to simulate flash errors
New module parameters have been added to nandsim to
simulate:
bitflips random bit flips
badblocks blocks that are initially marked bad
weakblocks blocks that fail to erase after a
small number of erase cycles
weakpages pages that fail to write after a
small number of successful writes
gravepages pages that fail to read after a
small number of successful reads
Signed-off-by: Adrian Hunter <ext-adrian.hunter@nokia.com>
Signed-off-by: Artem Bityutskiy <Artem.Bityutskiy@nokia.com>
Signed-off-by: David Woodhouse <dwmw2@infradead.org>
Diffstat (limited to 'drivers/mtd')
-rw-r--r-- | drivers/mtd/nand/nandsim.c | 301 |
1 files changed, 300 insertions, 1 deletions
diff --git a/drivers/mtd/nand/nandsim.c b/drivers/mtd/nand/nandsim.c index 638e6c256d3e..05b42077d22f 100644 --- a/drivers/mtd/nand/nandsim.c +++ b/drivers/mtd/nand/nandsim.c | |||
@@ -38,6 +38,7 @@ | |||
38 | #include <linux/mtd/partitions.h> | 38 | #include <linux/mtd/partitions.h> |
39 | #include <linux/delay.h> | 39 | #include <linux/delay.h> |
40 | #include <linux/list.h> | 40 | #include <linux/list.h> |
41 | #include <linux/random.h> | ||
41 | 42 | ||
42 | /* Default simulator parameters values */ | 43 | /* Default simulator parameters values */ |
43 | #if !defined(CONFIG_NANDSIM_FIRST_ID_BYTE) || \ | 44 | #if !defined(CONFIG_NANDSIM_FIRST_ID_BYTE) || \ |
@@ -93,6 +94,11 @@ static uint log = CONFIG_NANDSIM_LOG; | |||
93 | static uint dbg = CONFIG_NANDSIM_DBG; | 94 | static uint dbg = CONFIG_NANDSIM_DBG; |
94 | static unsigned long parts[MAX_MTD_DEVICES]; | 95 | static unsigned long parts[MAX_MTD_DEVICES]; |
95 | static unsigned int parts_num; | 96 | static unsigned int parts_num; |
97 | static char *badblocks = NULL; | ||
98 | static char *weakblocks = NULL; | ||
99 | static char *weakpages = NULL; | ||
100 | static unsigned int bitflips = 0; | ||
101 | static char *gravepages = NULL; | ||
96 | 102 | ||
97 | module_param(first_id_byte, uint, 0400); | 103 | module_param(first_id_byte, uint, 0400); |
98 | module_param(second_id_byte, uint, 0400); | 104 | module_param(second_id_byte, uint, 0400); |
@@ -108,6 +114,11 @@ module_param(do_delays, uint, 0400); | |||
108 | module_param(log, uint, 0400); | 114 | module_param(log, uint, 0400); |
109 | module_param(dbg, uint, 0400); | 115 | module_param(dbg, uint, 0400); |
110 | module_param_array(parts, ulong, &parts_num, 0400); | 116 | module_param_array(parts, ulong, &parts_num, 0400); |
117 | module_param(badblocks, charp, 0400); | ||
118 | module_param(weakblocks, charp, 0400); | ||
119 | module_param(weakpages, charp, 0400); | ||
120 | module_param(bitflips, uint, 0400); | ||
121 | module_param(gravepages, charp, 0400); | ||
111 | 122 | ||
112 | MODULE_PARM_DESC(first_id_byte, "The fist byte returned by NAND Flash 'read ID' command (manufaturer ID)"); | 123 | MODULE_PARM_DESC(first_id_byte, "The fist byte returned by NAND Flash 'read ID' command (manufaturer ID)"); |
113 | MODULE_PARM_DESC(second_id_byte, "The second byte returned by NAND Flash 'read ID' command (chip ID)"); | 124 | MODULE_PARM_DESC(second_id_byte, "The second byte returned by NAND Flash 'read ID' command (chip ID)"); |
@@ -123,6 +134,18 @@ MODULE_PARM_DESC(do_delays, "Simulate NAND delays using busy-waits if not z | |||
123 | MODULE_PARM_DESC(log, "Perform logging if not zero"); | 134 | MODULE_PARM_DESC(log, "Perform logging if not zero"); |
124 | MODULE_PARM_DESC(dbg, "Output debug information if not zero"); | 135 | MODULE_PARM_DESC(dbg, "Output debug information if not zero"); |
125 | MODULE_PARM_DESC(parts, "Partition sizes (in erase blocks) separated by commas"); | 136 | MODULE_PARM_DESC(parts, "Partition sizes (in erase blocks) separated by commas"); |
137 | /* Page and erase block positions for the following parameters are independent of any partitions */ | ||
138 | MODULE_PARM_DESC(badblocks, "Erase blocks that are initially marked bad, separated by commas"); | ||
139 | MODULE_PARM_DESC(weakblocks, "Weak erase blocks [: remaining erase cycles (defaults to 3)]" | ||
140 | " separated by commas e.g. 113:2 means eb 113" | ||
141 | " can be erased only twice before failing"); | ||
142 | MODULE_PARM_DESC(weakpages, "Weak pages [: maximum writes (defaults to 3)]" | ||
143 | " separated by commas e.g. 1401:2 means page 1401" | ||
144 | " can be written only twice before failing"); | ||
145 | MODULE_PARM_DESC(bitflips, "Maximum number of random bit flips per page (zero by default)"); | ||
146 | MODULE_PARM_DESC(gravepages, "Pages that lose data [: maximum reads (defaults to 3)]" | ||
147 | " separated by commas e.g. 1401:2 means page 1401" | ||
148 | " can be read only twice before failing"); | ||
126 | 149 | ||
127 | /* The largest possible page size */ | 150 | /* The largest possible page size */ |
128 | #define NS_LARGEST_PAGE_SIZE 2048 | 151 | #define NS_LARGEST_PAGE_SIZE 2048 |
@@ -344,6 +367,33 @@ static struct nandsim_operations { | |||
344 | STATE_DATAOUT, STATE_READY}} | 367 | STATE_DATAOUT, STATE_READY}} |
345 | }; | 368 | }; |
346 | 369 | ||
370 | struct weak_block { | ||
371 | struct list_head list; | ||
372 | unsigned int erase_block_no; | ||
373 | unsigned int max_erases; | ||
374 | unsigned int erases_done; | ||
375 | }; | ||
376 | |||
377 | static LIST_HEAD(weak_blocks); | ||
378 | |||
379 | struct weak_page { | ||
380 | struct list_head list; | ||
381 | unsigned int page_no; | ||
382 | unsigned int max_writes; | ||
383 | unsigned int writes_done; | ||
384 | }; | ||
385 | |||
386 | static LIST_HEAD(weak_pages); | ||
387 | |||
388 | struct grave_page { | ||
389 | struct list_head list; | ||
390 | unsigned int page_no; | ||
391 | unsigned int max_reads; | ||
392 | unsigned int reads_done; | ||
393 | }; | ||
394 | |||
395 | static LIST_HEAD(grave_pages); | ||
396 | |||
347 | /* MTD structure for NAND controller */ | 397 | /* MTD structure for NAND controller */ |
348 | static struct mtd_info *nsmtd; | 398 | static struct mtd_info *nsmtd; |
349 | 399 | ||
@@ -555,6 +605,204 @@ static void free_nandsim(struct nandsim *ns) | |||
555 | return; | 605 | return; |
556 | } | 606 | } |
557 | 607 | ||
608 | static int parse_badblocks(struct nandsim *ns, struct mtd_info *mtd) | ||
609 | { | ||
610 | char *w; | ||
611 | int zero_ok; | ||
612 | unsigned int erase_block_no; | ||
613 | loff_t offset; | ||
614 | |||
615 | if (!badblocks) | ||
616 | return 0; | ||
617 | w = badblocks; | ||
618 | do { | ||
619 | zero_ok = (*w == '0' ? 1 : 0); | ||
620 | erase_block_no = simple_strtoul(w, &w, 0); | ||
621 | if (!zero_ok && !erase_block_no) { | ||
622 | NS_ERR("invalid badblocks.\n"); | ||
623 | return -EINVAL; | ||
624 | } | ||
625 | offset = erase_block_no * ns->geom.secsz; | ||
626 | if (mtd->block_markbad(mtd, offset)) { | ||
627 | NS_ERR("invalid badblocks.\n"); | ||
628 | return -EINVAL; | ||
629 | } | ||
630 | if (*w == ',') | ||
631 | w += 1; | ||
632 | } while (*w); | ||
633 | return 0; | ||
634 | } | ||
635 | |||
636 | static int parse_weakblocks(void) | ||
637 | { | ||
638 | char *w; | ||
639 | int zero_ok; | ||
640 | unsigned int erase_block_no; | ||
641 | unsigned int max_erases; | ||
642 | struct weak_block *wb; | ||
643 | |||
644 | if (!weakblocks) | ||
645 | return 0; | ||
646 | w = weakblocks; | ||
647 | do { | ||
648 | zero_ok = (*w == '0' ? 1 : 0); | ||
649 | erase_block_no = simple_strtoul(w, &w, 0); | ||
650 | if (!zero_ok && !erase_block_no) { | ||
651 | NS_ERR("invalid weakblocks.\n"); | ||
652 | return -EINVAL; | ||
653 | } | ||
654 | max_erases = 3; | ||
655 | if (*w == ':') { | ||
656 | w += 1; | ||
657 | max_erases = simple_strtoul(w, &w, 0); | ||
658 | } | ||
659 | if (*w == ',') | ||
660 | w += 1; | ||
661 | wb = kzalloc(sizeof(*wb), GFP_KERNEL); | ||
662 | if (!wb) { | ||
663 | NS_ERR("unable to allocate memory.\n"); | ||
664 | return -ENOMEM; | ||
665 | } | ||
666 | wb->erase_block_no = erase_block_no; | ||
667 | wb->max_erases = max_erases; | ||
668 | list_add(&wb->list, &weak_blocks); | ||
669 | } while (*w); | ||
670 | return 0; | ||
671 | } | ||
672 | |||
673 | static int erase_error(unsigned int erase_block_no) | ||
674 | { | ||
675 | struct weak_block *wb; | ||
676 | |||
677 | list_for_each_entry(wb, &weak_blocks, list) | ||
678 | if (wb->erase_block_no == erase_block_no) { | ||
679 | if (wb->erases_done >= wb->max_erases) | ||
680 | return 1; | ||
681 | wb->erases_done += 1; | ||
682 | return 0; | ||
683 | } | ||
684 | return 0; | ||
685 | } | ||
686 | |||
687 | static int parse_weakpages(void) | ||
688 | { | ||
689 | char *w; | ||
690 | int zero_ok; | ||
691 | unsigned int page_no; | ||
692 | unsigned int max_writes; | ||
693 | struct weak_page *wp; | ||
694 | |||
695 | if (!weakpages) | ||
696 | return 0; | ||
697 | w = weakpages; | ||
698 | do { | ||
699 | zero_ok = (*w == '0' ? 1 : 0); | ||
700 | page_no = simple_strtoul(w, &w, 0); | ||
701 | if (!zero_ok && !page_no) { | ||
702 | NS_ERR("invalid weakpagess.\n"); | ||
703 | return -EINVAL; | ||
704 | } | ||
705 | max_writes = 3; | ||
706 | if (*w == ':') { | ||
707 | w += 1; | ||
708 | max_writes = simple_strtoul(w, &w, 0); | ||
709 | } | ||
710 | if (*w == ',') | ||
711 | w += 1; | ||
712 | wp = kzalloc(sizeof(*wp), GFP_KERNEL); | ||
713 | if (!wp) { | ||
714 | NS_ERR("unable to allocate memory.\n"); | ||
715 | return -ENOMEM; | ||
716 | } | ||
717 | wp->page_no = page_no; | ||
718 | wp->max_writes = max_writes; | ||
719 | list_add(&wp->list, &weak_pages); | ||
720 | } while (*w); | ||
721 | return 0; | ||
722 | } | ||
723 | |||
724 | static int write_error(unsigned int page_no) | ||
725 | { | ||
726 | struct weak_page *wp; | ||
727 | |||
728 | list_for_each_entry(wp, &weak_pages, list) | ||
729 | if (wp->page_no == page_no) { | ||
730 | if (wp->writes_done >= wp->max_writes) | ||
731 | return 1; | ||
732 | wp->writes_done += 1; | ||
733 | return 0; | ||
734 | } | ||
735 | return 0; | ||
736 | } | ||
737 | |||
738 | static int parse_gravepages(void) | ||
739 | { | ||
740 | char *g; | ||
741 | int zero_ok; | ||
742 | unsigned int page_no; | ||
743 | unsigned int max_reads; | ||
744 | struct grave_page *gp; | ||
745 | |||
746 | if (!gravepages) | ||
747 | return 0; | ||
748 | g = gravepages; | ||
749 | do { | ||
750 | zero_ok = (*g == '0' ? 1 : 0); | ||
751 | page_no = simple_strtoul(g, &g, 0); | ||
752 | if (!zero_ok && !page_no) { | ||
753 | NS_ERR("invalid gravepagess.\n"); | ||
754 | return -EINVAL; | ||
755 | } | ||
756 | max_reads = 3; | ||
757 | if (*g == ':') { | ||
758 | g += 1; | ||
759 | max_reads = simple_strtoul(g, &g, 0); | ||
760 | } | ||
761 | if (*g == ',') | ||
762 | g += 1; | ||
763 | gp = kzalloc(sizeof(*gp), GFP_KERNEL); | ||
764 | if (!gp) { | ||
765 | NS_ERR("unable to allocate memory.\n"); | ||
766 | return -ENOMEM; | ||
767 | } | ||
768 | gp->page_no = page_no; | ||
769 | gp->max_reads = max_reads; | ||
770 | list_add(&gp->list, &grave_pages); | ||
771 | } while (*g); | ||
772 | return 0; | ||
773 | } | ||
774 | |||
775 | static int read_error(unsigned int page_no) | ||
776 | { | ||
777 | struct grave_page *gp; | ||
778 | |||
779 | list_for_each_entry(gp, &grave_pages, list) | ||
780 | if (gp->page_no == page_no) { | ||
781 | if (gp->reads_done >= gp->max_reads) | ||
782 | return 1; | ||
783 | gp->reads_done += 1; | ||
784 | return 0; | ||
785 | } | ||
786 | return 0; | ||
787 | } | ||
788 | |||
789 | static void free_lists(void) | ||
790 | { | ||
791 | struct list_head *pos, *n; | ||
792 | list_for_each_safe(pos, n, &weak_blocks) { | ||
793 | list_del(pos); | ||
794 | kfree(list_entry(pos, struct weak_block, list)); | ||
795 | } | ||
796 | list_for_each_safe(pos, n, &weak_pages) { | ||
797 | list_del(pos); | ||
798 | kfree(list_entry(pos, struct weak_page, list)); | ||
799 | } | ||
800 | list_for_each_safe(pos, n, &grave_pages) { | ||
801 | list_del(pos); | ||
802 | kfree(list_entry(pos, struct grave_page, list)); | ||
803 | } | ||
804 | } | ||
805 | |||
558 | /* | 806 | /* |
559 | * Returns the string representation of 'state' state. | 807 | * Returns the string representation of 'state' state. |
560 | */ | 808 | */ |
@@ -867,9 +1115,31 @@ static void read_page(struct nandsim *ns, int num) | |||
867 | NS_DBG("read_page: page %d not allocated\n", ns->regs.row); | 1115 | NS_DBG("read_page: page %d not allocated\n", ns->regs.row); |
868 | memset(ns->buf.byte, 0xFF, num); | 1116 | memset(ns->buf.byte, 0xFF, num); |
869 | } else { | 1117 | } else { |
1118 | unsigned int page_no = ns->regs.row; | ||
870 | NS_DBG("read_page: page %d allocated, reading from %d\n", | 1119 | NS_DBG("read_page: page %d allocated, reading from %d\n", |
871 | ns->regs.row, ns->regs.column + ns->regs.off); | 1120 | ns->regs.row, ns->regs.column + ns->regs.off); |
1121 | if (read_error(page_no)) { | ||
1122 | int i; | ||
1123 | memset(ns->buf.byte, 0xFF, num); | ||
1124 | for (i = 0; i < num; ++i) | ||
1125 | ns->buf.byte[i] = random32(); | ||
1126 | NS_WARN("simulating read error in page %u\n", page_no); | ||
1127 | return; | ||
1128 | } | ||
872 | memcpy(ns->buf.byte, NS_PAGE_BYTE_OFF(ns), num); | 1129 | memcpy(ns->buf.byte, NS_PAGE_BYTE_OFF(ns), num); |
1130 | if (bitflips && random32() < (1 << 22)) { | ||
1131 | int flips = 1; | ||
1132 | if (bitflips > 1) | ||
1133 | flips = (random32() % (int) bitflips) + 1; | ||
1134 | while (flips--) { | ||
1135 | int pos = random32() % (num * 8); | ||
1136 | ns->buf.byte[pos / 8] ^= (1 << (pos % 8)); | ||
1137 | NS_WARN("read_page: flipping bit %d in page %d " | ||
1138 | "reading from %d ecc: corrected=%u failed=%u\n", | ||
1139 | pos, ns->regs.row, ns->regs.column + ns->regs.off, | ||
1140 | nsmtd->ecc_stats.corrected, nsmtd->ecc_stats.failed); | ||
1141 | } | ||
1142 | } | ||
873 | } | 1143 | } |
874 | } | 1144 | } |
875 | 1145 | ||
@@ -928,6 +1198,7 @@ static int do_state_action(struct nandsim *ns, uint32_t action) | |||
928 | { | 1198 | { |
929 | int num; | 1199 | int num; |
930 | int busdiv = ns->busw == 8 ? 1 : 2; | 1200 | int busdiv = ns->busw == 8 ? 1 : 2; |
1201 | unsigned int erase_block_no, page_no; | ||
931 | 1202 | ||
932 | action &= ACTION_MASK; | 1203 | action &= ACTION_MASK; |
933 | 1204 | ||
@@ -987,14 +1258,21 @@ static int do_state_action(struct nandsim *ns, uint32_t action) | |||
987 | 8 * (ns->geom.pgaddrbytes - ns->geom.secaddrbytes)) | ns->regs.column; | 1258 | 8 * (ns->geom.pgaddrbytes - ns->geom.secaddrbytes)) | ns->regs.column; |
988 | ns->regs.column = 0; | 1259 | ns->regs.column = 0; |
989 | 1260 | ||
1261 | erase_block_no = ns->regs.row >> (ns->geom.secshift - ns->geom.pgshift); | ||
1262 | |||
990 | NS_DBG("do_state_action: erase sector at address %#x, off = %d\n", | 1263 | NS_DBG("do_state_action: erase sector at address %#x, off = %d\n", |
991 | ns->regs.row, NS_RAW_OFFSET(ns)); | 1264 | ns->regs.row, NS_RAW_OFFSET(ns)); |
992 | NS_LOG("erase sector %d\n", ns->regs.row >> (ns->geom.secshift - ns->geom.pgshift)); | 1265 | NS_LOG("erase sector %u\n", erase_block_no); |
993 | 1266 | ||
994 | erase_sector(ns); | 1267 | erase_sector(ns); |
995 | 1268 | ||
996 | NS_MDELAY(erase_delay); | 1269 | NS_MDELAY(erase_delay); |
997 | 1270 | ||
1271 | if (erase_error(erase_block_no)) { | ||
1272 | NS_WARN("simulating erase failure in erase block %u\n", erase_block_no); | ||
1273 | return -1; | ||
1274 | } | ||
1275 | |||
998 | break; | 1276 | break; |
999 | 1277 | ||
1000 | case ACTION_PRGPAGE: | 1278 | case ACTION_PRGPAGE: |
@@ -1017,6 +1295,8 @@ static int do_state_action(struct nandsim *ns, uint32_t action) | |||
1017 | if (prog_page(ns, num) == -1) | 1295 | if (prog_page(ns, num) == -1) |
1018 | return -1; | 1296 | return -1; |
1019 | 1297 | ||
1298 | page_no = ns->regs.row; | ||
1299 | |||
1020 | NS_DBG("do_state_action: copy %d bytes from int buf to (%#x, %#x), raw off = %d\n", | 1300 | NS_DBG("do_state_action: copy %d bytes from int buf to (%#x, %#x), raw off = %d\n", |
1021 | num, ns->regs.row, ns->regs.column, NS_RAW_OFFSET(ns) + ns->regs.off); | 1301 | num, ns->regs.row, ns->regs.column, NS_RAW_OFFSET(ns) + ns->regs.off); |
1022 | NS_LOG("programm page %d\n", ns->regs.row); | 1302 | NS_LOG("programm page %d\n", ns->regs.row); |
@@ -1024,6 +1304,11 @@ static int do_state_action(struct nandsim *ns, uint32_t action) | |||
1024 | NS_UDELAY(programm_delay); | 1304 | NS_UDELAY(programm_delay); |
1025 | NS_UDELAY(output_cycle * ns->geom.pgsz / 1000 / busdiv); | 1305 | NS_UDELAY(output_cycle * ns->geom.pgsz / 1000 / busdiv); |
1026 | 1306 | ||
1307 | if (write_error(page_no)) { | ||
1308 | NS_WARN("simulating write failure in page %u\n", page_no); | ||
1309 | return -1; | ||
1310 | } | ||
1311 | |||
1027 | break; | 1312 | break; |
1028 | 1313 | ||
1029 | case ACTION_ZEROOFF: | 1314 | case ACTION_ZEROOFF: |
@@ -1602,6 +1887,15 @@ static int __init ns_init_module(void) | |||
1602 | 1887 | ||
1603 | nsmtd->owner = THIS_MODULE; | 1888 | nsmtd->owner = THIS_MODULE; |
1604 | 1889 | ||
1890 | if ((retval = parse_weakblocks()) != 0) | ||
1891 | goto error; | ||
1892 | |||
1893 | if ((retval = parse_weakpages()) != 0) | ||
1894 | goto error; | ||
1895 | |||
1896 | if ((retval = parse_gravepages()) != 0) | ||
1897 | goto error; | ||
1898 | |||
1605 | if ((retval = nand_scan(nsmtd, 1)) != 0) { | 1899 | if ((retval = nand_scan(nsmtd, 1)) != 0) { |
1606 | NS_ERR("can't register NAND Simulator\n"); | 1900 | NS_ERR("can't register NAND Simulator\n"); |
1607 | if (retval > 0) | 1901 | if (retval > 0) |
@@ -1612,6 +1906,9 @@ static int __init ns_init_module(void) | |||
1612 | if ((retval = init_nandsim(nsmtd)) != 0) | 1906 | if ((retval = init_nandsim(nsmtd)) != 0) |
1613 | goto err_exit; | 1907 | goto err_exit; |
1614 | 1908 | ||
1909 | if ((retval = parse_badblocks(nand, nsmtd)) != 0) | ||
1910 | goto err_exit; | ||
1911 | |||
1615 | if ((retval = nand_default_bbt(nsmtd)) != 0) | 1912 | if ((retval = nand_default_bbt(nsmtd)) != 0) |
1616 | goto err_exit; | 1913 | goto err_exit; |
1617 | 1914 | ||
@@ -1628,6 +1925,7 @@ err_exit: | |||
1628 | kfree(nand->partitions[i].name); | 1925 | kfree(nand->partitions[i].name); |
1629 | error: | 1926 | error: |
1630 | kfree(nsmtd); | 1927 | kfree(nsmtd); |
1928 | free_lists(); | ||
1631 | 1929 | ||
1632 | return retval; | 1930 | return retval; |
1633 | } | 1931 | } |
@@ -1647,6 +1945,7 @@ static void __exit ns_cleanup_module(void) | |||
1647 | for (i = 0;i < ARRAY_SIZE(ns->partitions); ++i) | 1945 | for (i = 0;i < ARRAY_SIZE(ns->partitions); ++i) |
1648 | kfree(ns->partitions[i].name); | 1946 | kfree(ns->partitions[i].name); |
1649 | kfree(nsmtd); /* Free other structures */ | 1947 | kfree(nsmtd); /* Free other structures */ |
1948 | free_lists(); | ||
1650 | } | 1949 | } |
1651 | 1950 | ||
1652 | module_exit(ns_cleanup_module); | 1951 | module_exit(ns_cleanup_module); |