diff options
author | Ezequiel Garcia <elezegarcia@gmail.com> | 2012-12-03 08:31:40 -0500 |
---|---|---|
committer | Artem Bityutskiy <artem.bityutskiy@linux.intel.com> | 2012-12-10 09:42:42 -0500 |
commit | 5346c27c5fed6b30aff35e1fcb595625097e646c (patch) | |
tree | c10264204d119f2ebee24ab858009302c3b6e2f9 /drivers/mtd | |
parent | e58a66d84bceba314b03e37ec0764b9b1b9227d0 (diff) |
mtd: nandsim: Introduce debugfs infrastructure
It's more user friendly to report debug information and statistics
through debugfs, than to use printing facilites.
This patch introduces a very minimal debugfs infrastructure
and moves eraseblock wear report to an entry located at:
/sys/kernel/debug/nandsim/wear_report
This means we can remove rptwear option and just let
the user get the wear report when we needs to.
Signed-off-by: Ezequiel Garcia <elezegarcia@gmail.com>
Signed-off-by: Artem Bityutskiy <artem.bityutskiy@linux.intel.com>
Diffstat (limited to 'drivers/mtd')
-rw-r--r-- | drivers/mtd/nand/nandsim.c | 186 |
1 files changed, 130 insertions, 56 deletions
diff --git a/drivers/mtd/nand/nandsim.c b/drivers/mtd/nand/nandsim.c index a932c485eb04..2ab827428f18 100644 --- a/drivers/mtd/nand/nandsim.c +++ b/drivers/mtd/nand/nandsim.c | |||
@@ -42,6 +42,8 @@ | |||
42 | #include <linux/sched.h> | 42 | #include <linux/sched.h> |
43 | #include <linux/fs.h> | 43 | #include <linux/fs.h> |
44 | #include <linux/pagemap.h> | 44 | #include <linux/pagemap.h> |
45 | #include <linux/seq_file.h> | ||
46 | #include <linux/debugfs.h> | ||
45 | 47 | ||
46 | /* Default simulator parameters values */ | 48 | /* Default simulator parameters values */ |
47 | #if !defined(CONFIG_NANDSIM_FIRST_ID_BYTE) || \ | 49 | #if !defined(CONFIG_NANDSIM_FIRST_ID_BYTE) || \ |
@@ -105,7 +107,6 @@ static char *weakblocks = NULL; | |||
105 | static char *weakpages = NULL; | 107 | static char *weakpages = NULL; |
106 | static unsigned int bitflips = 0; | 108 | static unsigned int bitflips = 0; |
107 | static char *gravepages = NULL; | 109 | static char *gravepages = NULL; |
108 | static unsigned int rptwear = 0; | ||
109 | static unsigned int overridesize = 0; | 110 | static unsigned int overridesize = 0; |
110 | static char *cache_file = NULL; | 111 | static char *cache_file = NULL; |
111 | static unsigned int bbt; | 112 | static unsigned int bbt; |
@@ -130,7 +131,6 @@ module_param(weakblocks, charp, 0400); | |||
130 | module_param(weakpages, charp, 0400); | 131 | module_param(weakpages, charp, 0400); |
131 | module_param(bitflips, uint, 0400); | 132 | module_param(bitflips, uint, 0400); |
132 | module_param(gravepages, charp, 0400); | 133 | module_param(gravepages, charp, 0400); |
133 | module_param(rptwear, uint, 0400); | ||
134 | module_param(overridesize, uint, 0400); | 134 | module_param(overridesize, uint, 0400); |
135 | module_param(cache_file, charp, 0400); | 135 | module_param(cache_file, charp, 0400); |
136 | module_param(bbt, uint, 0400); | 136 | module_param(bbt, uint, 0400); |
@@ -162,7 +162,6 @@ MODULE_PARM_DESC(bitflips, "Maximum number of random bit flips per page (z | |||
162 | MODULE_PARM_DESC(gravepages, "Pages that lose data [: maximum reads (defaults to 3)]" | 162 | MODULE_PARM_DESC(gravepages, "Pages that lose data [: maximum reads (defaults to 3)]" |
163 | " separated by commas e.g. 1401:2 means page 1401" | 163 | " separated by commas e.g. 1401:2 means page 1401" |
164 | " can be read only twice before failing"); | 164 | " can be read only twice before failing"); |
165 | MODULE_PARM_DESC(rptwear, "Number of erases between reporting wear, if not zero"); | ||
166 | MODULE_PARM_DESC(overridesize, "Specifies the NAND Flash size overriding the ID bytes. " | 165 | MODULE_PARM_DESC(overridesize, "Specifies the NAND Flash size overriding the ID bytes. " |
167 | "The size is specified in erase blocks and as the exponent of a power of two" | 166 | "The size is specified in erase blocks and as the exponent of a power of two" |
168 | " e.g. 5 means a size of 32 erase blocks"); | 167 | " e.g. 5 means a size of 32 erase blocks"); |
@@ -286,6 +285,11 @@ MODULE_PARM_DESC(bch, "Enable BCH ecc and set how many bits should " | |||
286 | /* Maximum page cache pages needed to read or write a NAND page to the cache_file */ | 285 | /* Maximum page cache pages needed to read or write a NAND page to the cache_file */ |
287 | #define NS_MAX_HELD_PAGES 16 | 286 | #define NS_MAX_HELD_PAGES 16 |
288 | 287 | ||
288 | struct nandsim_debug_info { | ||
289 | struct dentry *dfs_root; | ||
290 | struct dentry *dfs_wear_report; | ||
291 | }; | ||
292 | |||
289 | /* | 293 | /* |
290 | * A union to represent flash memory contents and flash buffer. | 294 | * A union to represent flash memory contents and flash buffer. |
291 | */ | 295 | */ |
@@ -365,6 +369,8 @@ struct nandsim { | |||
365 | void *file_buf; | 369 | void *file_buf; |
366 | struct page *held_pages[NS_MAX_HELD_PAGES]; | 370 | struct page *held_pages[NS_MAX_HELD_PAGES]; |
367 | int held_cnt; | 371 | int held_cnt; |
372 | |||
373 | struct nandsim_debug_info dbg; | ||
368 | }; | 374 | }; |
369 | 375 | ||
370 | /* | 376 | /* |
@@ -442,11 +448,123 @@ static LIST_HEAD(grave_pages); | |||
442 | static unsigned long *erase_block_wear = NULL; | 448 | static unsigned long *erase_block_wear = NULL; |
443 | static unsigned int wear_eb_count = 0; | 449 | static unsigned int wear_eb_count = 0; |
444 | static unsigned long total_wear = 0; | 450 | static unsigned long total_wear = 0; |
445 | static unsigned int rptwear_cnt = 0; | ||
446 | 451 | ||
447 | /* MTD structure for NAND controller */ | 452 | /* MTD structure for NAND controller */ |
448 | static struct mtd_info *nsmtd; | 453 | static struct mtd_info *nsmtd; |
449 | 454 | ||
455 | static int nandsim_debugfs_show(struct seq_file *m, void *private) | ||
456 | { | ||
457 | unsigned long wmin = -1, wmax = 0, avg; | ||
458 | unsigned long deciles[10], decile_max[10], tot = 0; | ||
459 | unsigned int i; | ||
460 | |||
461 | /* Calc wear stats */ | ||
462 | for (i = 0; i < wear_eb_count; ++i) { | ||
463 | unsigned long wear = erase_block_wear[i]; | ||
464 | if (wear < wmin) | ||
465 | wmin = wear; | ||
466 | if (wear > wmax) | ||
467 | wmax = wear; | ||
468 | tot += wear; | ||
469 | } | ||
470 | |||
471 | for (i = 0; i < 9; ++i) { | ||
472 | deciles[i] = 0; | ||
473 | decile_max[i] = (wmax * (i + 1) + 5) / 10; | ||
474 | } | ||
475 | deciles[9] = 0; | ||
476 | decile_max[9] = wmax; | ||
477 | for (i = 0; i < wear_eb_count; ++i) { | ||
478 | int d; | ||
479 | unsigned long wear = erase_block_wear[i]; | ||
480 | for (d = 0; d < 10; ++d) | ||
481 | if (wear <= decile_max[d]) { | ||
482 | deciles[d] += 1; | ||
483 | break; | ||
484 | } | ||
485 | } | ||
486 | avg = tot / wear_eb_count; | ||
487 | |||
488 | /* Output wear report */ | ||
489 | seq_printf(m, "Total numbers of erases: %lu\n", tot); | ||
490 | seq_printf(m, "Number of erase blocks: %u\n", wear_eb_count); | ||
491 | seq_printf(m, "Average number of erases: %lu\n", avg); | ||
492 | seq_printf(m, "Maximum number of erases: %lu\n", wmax); | ||
493 | seq_printf(m, "Minimum number of erases: %lu\n", wmin); | ||
494 | for (i = 0; i < 10; ++i) { | ||
495 | unsigned long from = (i ? decile_max[i - 1] + 1 : 0); | ||
496 | if (from > decile_max[i]) | ||
497 | continue; | ||
498 | seq_printf(m, "Number of ebs with erase counts from %lu to %lu : %lu\n", | ||
499 | from, | ||
500 | decile_max[i], | ||
501 | deciles[i]); | ||
502 | } | ||
503 | |||
504 | return 0; | ||
505 | } | ||
506 | |||
507 | static int nandsim_debugfs_open(struct inode *inode, struct file *file) | ||
508 | { | ||
509 | return single_open(file, nandsim_debugfs_show, inode->i_private); | ||
510 | } | ||
511 | |||
512 | static const struct file_operations dfs_fops = { | ||
513 | .open = nandsim_debugfs_open, | ||
514 | .read = seq_read, | ||
515 | .llseek = seq_lseek, | ||
516 | .release = single_release, | ||
517 | }; | ||
518 | |||
519 | /** | ||
520 | * nandsim_debugfs_create - initialize debugfs | ||
521 | * @dev: nandsim device description object | ||
522 | * | ||
523 | * This function creates all debugfs files for UBI device @ubi. Returns zero in | ||
524 | * case of success and a negative error code in case of failure. | ||
525 | */ | ||
526 | static int nandsim_debugfs_create(struct nandsim *dev) | ||
527 | { | ||
528 | struct nandsim_debug_info *dbg = &dev->dbg; | ||
529 | struct dentry *dent; | ||
530 | int err; | ||
531 | |||
532 | if (!IS_ENABLED(CONFIG_DEBUG_FS)) | ||
533 | return 0; | ||
534 | |||
535 | dent = debugfs_create_dir("nandsim", NULL); | ||
536 | if (IS_ERR_OR_NULL(dent)) { | ||
537 | int err = dent ? -ENODEV : PTR_ERR(dent); | ||
538 | |||
539 | NS_ERR("cannot create \"nandsim\" debugfs directory, err %d\n", | ||
540 | err); | ||
541 | return err; | ||
542 | } | ||
543 | dbg->dfs_root = dent; | ||
544 | |||
545 | dent = debugfs_create_file("wear_report", S_IRUSR, | ||
546 | dbg->dfs_root, dev, &dfs_fops); | ||
547 | if (IS_ERR_OR_NULL(dent)) | ||
548 | goto out_remove; | ||
549 | dbg->dfs_wear_report = dent; | ||
550 | |||
551 | return 0; | ||
552 | |||
553 | out_remove: | ||
554 | debugfs_remove_recursive(dbg->dfs_root); | ||
555 | err = dent ? PTR_ERR(dent) : -ENODEV; | ||
556 | return err; | ||
557 | } | ||
558 | |||
559 | /** | ||
560 | * nandsim_debugfs_remove - destroy all debugfs files | ||
561 | */ | ||
562 | static void nandsim_debugfs_remove(struct nandsim *ns) | ||
563 | { | ||
564 | if (IS_ENABLED(CONFIG_DEBUG_FS)) | ||
565 | debugfs_remove_recursive(ns->dbg.dfs_root); | ||
566 | } | ||
567 | |||
450 | /* | 568 | /* |
451 | * Allocate array of page pointers, create slab allocation for an array | 569 | * Allocate array of page pointers, create slab allocation for an array |
452 | * and initialize the array by NULL pointers. | 570 | * and initialize the array by NULL pointers. |
@@ -911,8 +1029,6 @@ static int setup_wear_reporting(struct mtd_info *mtd) | |||
911 | { | 1029 | { |
912 | size_t mem; | 1030 | size_t mem; |
913 | 1031 | ||
914 | if (!rptwear) | ||
915 | return 0; | ||
916 | wear_eb_count = div_u64(mtd->size, mtd->erasesize); | 1032 | wear_eb_count = div_u64(mtd->size, mtd->erasesize); |
917 | mem = wear_eb_count * sizeof(unsigned long); | 1033 | mem = wear_eb_count * sizeof(unsigned long); |
918 | if (mem / sizeof(unsigned long) != wear_eb_count) { | 1034 | if (mem / sizeof(unsigned long) != wear_eb_count) { |
@@ -929,64 +1045,18 @@ static int setup_wear_reporting(struct mtd_info *mtd) | |||
929 | 1045 | ||
930 | static void update_wear(unsigned int erase_block_no) | 1046 | static void update_wear(unsigned int erase_block_no) |
931 | { | 1047 | { |
932 | unsigned long wmin = -1, wmax = 0, avg; | ||
933 | unsigned long deciles[10], decile_max[10], tot = 0; | ||
934 | unsigned int i; | ||
935 | |||
936 | if (!erase_block_wear) | 1048 | if (!erase_block_wear) |
937 | return; | 1049 | return; |
938 | total_wear += 1; | 1050 | total_wear += 1; |
1051 | /* | ||
1052 | * TODO: Notify this through a debugfs entry, | ||
1053 | * instead of showing an error message. | ||
1054 | */ | ||
939 | if (total_wear == 0) | 1055 | if (total_wear == 0) |
940 | NS_ERR("Erase counter total overflow\n"); | 1056 | NS_ERR("Erase counter total overflow\n"); |
941 | erase_block_wear[erase_block_no] += 1; | 1057 | erase_block_wear[erase_block_no] += 1; |
942 | if (erase_block_wear[erase_block_no] == 0) | 1058 | if (erase_block_wear[erase_block_no] == 0) |
943 | NS_ERR("Erase counter overflow for erase block %u\n", erase_block_no); | 1059 | NS_ERR("Erase counter overflow for erase block %u\n", erase_block_no); |
944 | rptwear_cnt += 1; | ||
945 | if (rptwear_cnt < rptwear) | ||
946 | return; | ||
947 | rptwear_cnt = 0; | ||
948 | /* Calc wear stats */ | ||
949 | for (i = 0; i < wear_eb_count; ++i) { | ||
950 | unsigned long wear = erase_block_wear[i]; | ||
951 | if (wear < wmin) | ||
952 | wmin = wear; | ||
953 | if (wear > wmax) | ||
954 | wmax = wear; | ||
955 | tot += wear; | ||
956 | } | ||
957 | for (i = 0; i < 9; ++i) { | ||
958 | deciles[i] = 0; | ||
959 | decile_max[i] = (wmax * (i + 1) + 5) / 10; | ||
960 | } | ||
961 | deciles[9] = 0; | ||
962 | decile_max[9] = wmax; | ||
963 | for (i = 0; i < wear_eb_count; ++i) { | ||
964 | int d; | ||
965 | unsigned long wear = erase_block_wear[i]; | ||
966 | for (d = 0; d < 10; ++d) | ||
967 | if (wear <= decile_max[d]) { | ||
968 | deciles[d] += 1; | ||
969 | break; | ||
970 | } | ||
971 | } | ||
972 | avg = tot / wear_eb_count; | ||
973 | /* Output wear report */ | ||
974 | NS_INFO("*** Wear Report ***\n"); | ||
975 | NS_INFO("Total numbers of erases: %lu\n", tot); | ||
976 | NS_INFO("Number of erase blocks: %u\n", wear_eb_count); | ||
977 | NS_INFO("Average number of erases: %lu\n", avg); | ||
978 | NS_INFO("Maximum number of erases: %lu\n", wmax); | ||
979 | NS_INFO("Minimum number of erases: %lu\n", wmin); | ||
980 | for (i = 0; i < 10; ++i) { | ||
981 | unsigned long from = (i ? decile_max[i - 1] + 1 : 0); | ||
982 | if (from > decile_max[i]) | ||
983 | continue; | ||
984 | NS_INFO("Number of ebs with erase counts from %lu to %lu : %lu\n", | ||
985 | from, | ||
986 | decile_max[i], | ||
987 | deciles[i]); | ||
988 | } | ||
989 | NS_INFO("*** End of Wear Report ***\n"); | ||
990 | } | 1060 | } |
991 | 1061 | ||
992 | /* | 1062 | /* |
@@ -2330,6 +2400,9 @@ static int __init ns_init_module(void) | |||
2330 | if ((retval = setup_wear_reporting(nsmtd)) != 0) | 2400 | if ((retval = setup_wear_reporting(nsmtd)) != 0) |
2331 | goto err_exit; | 2401 | goto err_exit; |
2332 | 2402 | ||
2403 | if ((retval = nandsim_debugfs_create(nand)) != 0) | ||
2404 | goto err_exit; | ||
2405 | |||
2333 | if ((retval = init_nandsim(nsmtd)) != 0) | 2406 | if ((retval = init_nandsim(nsmtd)) != 0) |
2334 | goto err_exit; | 2407 | goto err_exit; |
2335 | 2408 | ||
@@ -2369,6 +2442,7 @@ static void __exit ns_cleanup_module(void) | |||
2369 | struct nandsim *ns = ((struct nand_chip *)nsmtd->priv)->priv; | 2442 | struct nandsim *ns = ((struct nand_chip *)nsmtd->priv)->priv; |
2370 | int i; | 2443 | int i; |
2371 | 2444 | ||
2445 | nandsim_debugfs_remove(ns); | ||
2372 | free_nandsim(ns); /* Free nandsim private resources */ | 2446 | free_nandsim(ns); /* Free nandsim private resources */ |
2373 | nand_release(nsmtd); /* Unregister driver */ | 2447 | nand_release(nsmtd); /* Unregister driver */ |
2374 | for (i = 0;i < ARRAY_SIZE(ns->partitions); ++i) | 2448 | for (i = 0;i < ARRAY_SIZE(ns->partitions); ++i) |