diff options
| author | Adrian Hunter <ext-adrian.hunter@nokia.com> | 2007-03-19 06:40:41 -0400 |
|---|---|---|
| committer | David Woodhouse <dwmw2@infradead.org> | 2007-04-17 13:55:29 -0400 |
| commit | 57aa6b545f6f772dd317ccd29bdada999b16a13d (patch) | |
| tree | efed75ea30b11cec3deaeb8025381f68a8d1ff42 | |
| parent | 514087e74fb401a6621e8c836f4eaab87c269f24 (diff) | |
[MTD] nandsim: Enhance nandsim optionally to report wear information
A new module parameter 'rptwear' specifies how many erases between
reporting wear information. Zero means never.
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>
| -rw-r--r-- | drivers/mtd/nand/nandsim.c | 99 |
1 files changed, 99 insertions, 0 deletions
diff --git a/drivers/mtd/nand/nandsim.c b/drivers/mtd/nand/nandsim.c index 05b42077d22f..1a44ef63c8d1 100644 --- a/drivers/mtd/nand/nandsim.c +++ b/drivers/mtd/nand/nandsim.c | |||
| @@ -99,6 +99,7 @@ static char *weakblocks = NULL; | |||
| 99 | static char *weakpages = NULL; | 99 | static char *weakpages = NULL; |
| 100 | static unsigned int bitflips = 0; | 100 | static unsigned int bitflips = 0; |
| 101 | static char *gravepages = NULL; | 101 | static char *gravepages = NULL; |
| 102 | static unsigned int rptwear = 0; | ||
| 102 | 103 | ||
| 103 | module_param(first_id_byte, uint, 0400); | 104 | module_param(first_id_byte, uint, 0400); |
| 104 | module_param(second_id_byte, uint, 0400); | 105 | module_param(second_id_byte, uint, 0400); |
| @@ -119,6 +120,7 @@ module_param(weakblocks, charp, 0400); | |||
| 119 | module_param(weakpages, charp, 0400); | 120 | module_param(weakpages, charp, 0400); |
| 120 | module_param(bitflips, uint, 0400); | 121 | module_param(bitflips, uint, 0400); |
| 121 | module_param(gravepages, charp, 0400); | 122 | module_param(gravepages, charp, 0400); |
| 123 | module_param(rptwear, uint, 0400); | ||
| 122 | 124 | ||
| 123 | MODULE_PARM_DESC(first_id_byte, "The fist byte returned by NAND Flash 'read ID' command (manufaturer ID)"); | 125 | MODULE_PARM_DESC(first_id_byte, "The fist byte returned by NAND Flash 'read ID' command (manufaturer ID)"); |
| 124 | MODULE_PARM_DESC(second_id_byte, "The second byte returned by NAND Flash 'read ID' command (chip ID)"); | 126 | MODULE_PARM_DESC(second_id_byte, "The second byte returned by NAND Flash 'read ID' command (chip ID)"); |
| @@ -146,6 +148,7 @@ MODULE_PARM_DESC(bitflips, "Maximum number of random bit flips per page (z | |||
| 146 | MODULE_PARM_DESC(gravepages, "Pages that lose data [: maximum reads (defaults to 3)]" | 148 | MODULE_PARM_DESC(gravepages, "Pages that lose data [: maximum reads (defaults to 3)]" |
| 147 | " separated by commas e.g. 1401:2 means page 1401" | 149 | " separated by commas e.g. 1401:2 means page 1401" |
| 148 | " can be read only twice before failing"); | 150 | " can be read only twice before failing"); |
| 151 | MODULE_PARM_DESC(rptwear, "Number of erases inbetween reporting wear, if not zero"); | ||
| 149 | 152 | ||
| 150 | /* The largest possible page size */ | 153 | /* The largest possible page size */ |
| 151 | #define NS_LARGEST_PAGE_SIZE 2048 | 154 | #define NS_LARGEST_PAGE_SIZE 2048 |
| @@ -162,6 +165,8 @@ MODULE_PARM_DESC(gravepages, "Pages that lose data [: maximum reads (default | |||
| 162 | do { printk(KERN_WARNING NS_OUTPUT_PREFIX " warning: " args); } while(0) | 165 | do { printk(KERN_WARNING NS_OUTPUT_PREFIX " warning: " args); } while(0) |
| 163 | #define NS_ERR(args...) \ | 166 | #define NS_ERR(args...) \ |
| 164 | do { printk(KERN_ERR NS_OUTPUT_PREFIX " error: " args); } while(0) | 167 | do { printk(KERN_ERR NS_OUTPUT_PREFIX " error: " args); } while(0) |
| 168 | #define NS_INFO(args...) \ | ||
| 169 | do { printk(KERN_INFO NS_OUTPUT_PREFIX " " args); } while(0) | ||
| 165 | 170 | ||
| 166 | /* Busy-wait delay macros (microseconds, milliseconds) */ | 171 | /* Busy-wait delay macros (microseconds, milliseconds) */ |
| 167 | #define NS_UDELAY(us) \ | 172 | #define NS_UDELAY(us) \ |
| @@ -394,6 +399,11 @@ struct grave_page { | |||
| 394 | 399 | ||
| 395 | static LIST_HEAD(grave_pages); | 400 | static LIST_HEAD(grave_pages); |
| 396 | 401 | ||
| 402 | static unsigned long *erase_block_wear = NULL; | ||
| 403 | static unsigned int wear_eb_count = 0; | ||
| 404 | static unsigned long total_wear = 0; | ||
| 405 | static unsigned int rptwear_cnt = 0; | ||
| 406 | |||
| 397 | /* MTD structure for NAND controller */ | 407 | /* MTD structure for NAND controller */ |
| 398 | static struct mtd_info *nsmtd; | 408 | static struct mtd_info *nsmtd; |
| 399 | 409 | ||
| @@ -801,6 +811,89 @@ static void free_lists(void) | |||
| 801 | list_del(pos); | 811 | list_del(pos); |
| 802 | kfree(list_entry(pos, struct grave_page, list)); | 812 | kfree(list_entry(pos, struct grave_page, list)); |
| 803 | } | 813 | } |
| 814 | kfree(erase_block_wear); | ||
| 815 | } | ||
| 816 | |||
| 817 | static int setup_wear_reporting(struct mtd_info *mtd) | ||
| 818 | { | ||
| 819 | size_t mem; | ||
| 820 | |||
| 821 | if (!rptwear) | ||
| 822 | return 0; | ||
| 823 | wear_eb_count = mtd->size / mtd->erasesize; | ||
| 824 | mem = wear_eb_count * sizeof(unsigned long); | ||
| 825 | if (mem / sizeof(unsigned long) != wear_eb_count) { | ||
| 826 | NS_ERR("Too many erase blocks for wear reporting\n"); | ||
| 827 | return -ENOMEM; | ||
| 828 | } | ||
| 829 | erase_block_wear = kzalloc(mem, GFP_KERNEL); | ||
| 830 | if (!erase_block_wear) { | ||
| 831 | NS_ERR("Too many erase blocks for wear reporting\n"); | ||
| 832 | return -ENOMEM; | ||
| 833 | } | ||
| 834 | return 0; | ||
| 835 | } | ||
| 836 | |||
| 837 | static void update_wear(unsigned int erase_block_no) | ||
| 838 | { | ||
| 839 | unsigned long wmin = -1, wmax = 0, avg; | ||
| 840 | unsigned long deciles[10], decile_max[10], tot = 0; | ||
| 841 | unsigned int i; | ||
| 842 | |||
| 843 | if (!erase_block_wear) | ||
| 844 | return; | ||
| 845 | total_wear += 1; | ||
| 846 | if (total_wear == 0) | ||
| 847 | NS_ERR("Erase counter total overflow\n"); | ||
| 848 | erase_block_wear[erase_block_no] += 1; | ||
| 849 | if (erase_block_wear[erase_block_no] == 0) | ||
| 850 | NS_ERR("Erase counter overflow for erase block %u\n", erase_block_no); | ||
| 851 | rptwear_cnt += 1; | ||
| 852 | if (rptwear_cnt < rptwear) | ||
| 853 | return; | ||
| 854 | rptwear_cnt = 0; | ||
| 855 | /* Calc wear stats */ | ||
| 856 | for (i = 0; i < wear_eb_count; ++i) { | ||
| 857 | unsigned long wear = erase_block_wear[i]; | ||
| 858 | if (wear < wmin) | ||
| 859 | wmin = wear; | ||
| 860 | if (wear > wmax) | ||
| 861 | wmax = wear; | ||
| 862 | tot += wear; | ||
| 863 | } | ||
| 864 | for (i = 0; i < 9; ++i) { | ||
| 865 | deciles[i] = 0; | ||
| 866 | decile_max[i] = (wmax * (i + 1) + 5) / 10; | ||
| 867 | } | ||
| 868 | deciles[9] = 0; | ||
| 869 | decile_max[9] = wmax; | ||
| 870 | for (i = 0; i < wear_eb_count; ++i) { | ||
| 871 | int d; | ||
| 872 | unsigned long wear = erase_block_wear[i]; | ||
| 873 | for (d = 0; d < 10; ++d) | ||
| 874 | if (wear <= decile_max[d]) { | ||
| 875 | deciles[d] += 1; | ||
| 876 | break; | ||
| 877 | } | ||
| 878 | } | ||
| 879 | avg = tot / wear_eb_count; | ||
| 880 | /* Output wear report */ | ||
| 881 | NS_INFO("*** Wear Report ***\n"); | ||
| 882 | NS_INFO("Total numbers of erases: %lu\n", tot); | ||
| 883 | NS_INFO("Number of erase blocks: %u\n", wear_eb_count); | ||
| 884 | NS_INFO("Average number of erases: %lu\n", avg); | ||
| 885 | NS_INFO("Maximum number of erases: %lu\n", wmax); | ||
| 886 | NS_INFO("Minimum number of erases: %lu\n", wmin); | ||
| 887 | for (i = 0; i < 10; ++i) { | ||
| 888 | unsigned long from = (i ? decile_max[i - 1] + 1 : 0); | ||
| 889 | if (from > decile_max[i]) | ||
| 890 | continue; | ||
| 891 | NS_INFO("Number of ebs with erase counts from %lu to %lu : %lu\n", | ||
| 892 | from, | ||
| 893 | decile_max[i], | ||
| 894 | deciles[i]); | ||
| 895 | } | ||
| 896 | NS_INFO("*** End of Wear Report ***\n"); | ||
| 804 | } | 897 | } |
| 805 | 898 | ||
| 806 | /* | 899 | /* |
| @@ -1268,6 +1361,9 @@ static int do_state_action(struct nandsim *ns, uint32_t action) | |||
| 1268 | 1361 | ||
| 1269 | NS_MDELAY(erase_delay); | 1362 | NS_MDELAY(erase_delay); |
| 1270 | 1363 | ||
| 1364 | if (erase_block_wear) | ||
| 1365 | update_wear(erase_block_no); | ||
| 1366 | |||
| 1271 | if (erase_error(erase_block_no)) { | 1367 | if (erase_error(erase_block_no)) { |
| 1272 | NS_WARN("simulating erase failure in erase block %u\n", erase_block_no); | 1368 | NS_WARN("simulating erase failure in erase block %u\n", erase_block_no); |
| 1273 | return -1; | 1369 | return -1; |
| @@ -1903,6 +1999,9 @@ static int __init ns_init_module(void) | |||
| 1903 | goto error; | 1999 | goto error; |
| 1904 | } | 2000 | } |
| 1905 | 2001 | ||
| 2002 | if ((retval = setup_wear_reporting(nsmtd)) != 0) | ||
| 2003 | goto err_exit; | ||
| 2004 | |||
| 1906 | if ((retval = init_nandsim(nsmtd)) != 0) | 2005 | if ((retval = init_nandsim(nsmtd)) != 0) |
| 1907 | goto err_exit; | 2006 | goto err_exit; |
| 1908 | 2007 | ||
