diff options
| -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) |
