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>
This commit is contained in:
parent
e58a66d84b
commit
5346c27c5f
@ -42,6 +42,8 @@
|
|||||||
#include <linux/sched.h>
|
#include <linux/sched.h>
|
||||||
#include <linux/fs.h>
|
#include <linux/fs.h>
|
||||||
#include <linux/pagemap.h>
|
#include <linux/pagemap.h>
|
||||||
|
#include <linux/seq_file.h>
|
||||||
|
#include <linux/debugfs.h>
|
||||||
|
|
||||||
/* Default simulator parameters values */
|
/* Default simulator parameters values */
|
||||||
#if !defined(CONFIG_NANDSIM_FIRST_ID_BYTE) || \
|
#if !defined(CONFIG_NANDSIM_FIRST_ID_BYTE) || \
|
||||||
@ -105,7 +107,6 @@ static char *weakblocks = NULL;
|
|||||||
static char *weakpages = NULL;
|
static char *weakpages = NULL;
|
||||||
static unsigned int bitflips = 0;
|
static unsigned int bitflips = 0;
|
||||||
static char *gravepages = NULL;
|
static char *gravepages = NULL;
|
||||||
static unsigned int rptwear = 0;
|
|
||||||
static unsigned int overridesize = 0;
|
static unsigned int overridesize = 0;
|
||||||
static char *cache_file = NULL;
|
static char *cache_file = NULL;
|
||||||
static unsigned int bbt;
|
static unsigned int bbt;
|
||||||
@ -130,7 +131,6 @@ module_param(weakblocks, charp, 0400);
|
|||||||
module_param(weakpages, charp, 0400);
|
module_param(weakpages, charp, 0400);
|
||||||
module_param(bitflips, uint, 0400);
|
module_param(bitflips, uint, 0400);
|
||||||
module_param(gravepages, charp, 0400);
|
module_param(gravepages, charp, 0400);
|
||||||
module_param(rptwear, uint, 0400);
|
|
||||||
module_param(overridesize, uint, 0400);
|
module_param(overridesize, uint, 0400);
|
||||||
module_param(cache_file, charp, 0400);
|
module_param(cache_file, charp, 0400);
|
||||||
module_param(bbt, uint, 0400);
|
module_param(bbt, uint, 0400);
|
||||||
@ -162,7 +162,6 @@ MODULE_PARM_DESC(bitflips, "Maximum number of random bit flips per page (z
|
|||||||
MODULE_PARM_DESC(gravepages, "Pages that lose data [: maximum reads (defaults to 3)]"
|
MODULE_PARM_DESC(gravepages, "Pages that lose data [: maximum reads (defaults to 3)]"
|
||||||
" separated by commas e.g. 1401:2 means page 1401"
|
" separated by commas e.g. 1401:2 means page 1401"
|
||||||
" can be read only twice before failing");
|
" can be read only twice before failing");
|
||||||
MODULE_PARM_DESC(rptwear, "Number of erases between reporting wear, if not zero");
|
|
||||||
MODULE_PARM_DESC(overridesize, "Specifies the NAND Flash size overriding the ID bytes. "
|
MODULE_PARM_DESC(overridesize, "Specifies the NAND Flash size overriding the ID bytes. "
|
||||||
"The size is specified in erase blocks and as the exponent of a power of two"
|
"The size is specified in erase blocks and as the exponent of a power of two"
|
||||||
" e.g. 5 means a size of 32 erase blocks");
|
" 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 "
|
|||||||
/* Maximum page cache pages needed to read or write a NAND page to the cache_file */
|
/* Maximum page cache pages needed to read or write a NAND page to the cache_file */
|
||||||
#define NS_MAX_HELD_PAGES 16
|
#define NS_MAX_HELD_PAGES 16
|
||||||
|
|
||||||
|
struct nandsim_debug_info {
|
||||||
|
struct dentry *dfs_root;
|
||||||
|
struct dentry *dfs_wear_report;
|
||||||
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* A union to represent flash memory contents and flash buffer.
|
* A union to represent flash memory contents and flash buffer.
|
||||||
*/
|
*/
|
||||||
@ -365,6 +369,8 @@ struct nandsim {
|
|||||||
void *file_buf;
|
void *file_buf;
|
||||||
struct page *held_pages[NS_MAX_HELD_PAGES];
|
struct page *held_pages[NS_MAX_HELD_PAGES];
|
||||||
int held_cnt;
|
int held_cnt;
|
||||||
|
|
||||||
|
struct nandsim_debug_info dbg;
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -442,11 +448,123 @@ static LIST_HEAD(grave_pages);
|
|||||||
static unsigned long *erase_block_wear = NULL;
|
static unsigned long *erase_block_wear = NULL;
|
||||||
static unsigned int wear_eb_count = 0;
|
static unsigned int wear_eb_count = 0;
|
||||||
static unsigned long total_wear = 0;
|
static unsigned long total_wear = 0;
|
||||||
static unsigned int rptwear_cnt = 0;
|
|
||||||
|
|
||||||
/* MTD structure for NAND controller */
|
/* MTD structure for NAND controller */
|
||||||
static struct mtd_info *nsmtd;
|
static struct mtd_info *nsmtd;
|
||||||
|
|
||||||
|
static int nandsim_debugfs_show(struct seq_file *m, void *private)
|
||||||
|
{
|
||||||
|
unsigned long wmin = -1, wmax = 0, avg;
|
||||||
|
unsigned long deciles[10], decile_max[10], tot = 0;
|
||||||
|
unsigned int i;
|
||||||
|
|
||||||
|
/* Calc wear stats */
|
||||||
|
for (i = 0; i < wear_eb_count; ++i) {
|
||||||
|
unsigned long wear = erase_block_wear[i];
|
||||||
|
if (wear < wmin)
|
||||||
|
wmin = wear;
|
||||||
|
if (wear > wmax)
|
||||||
|
wmax = wear;
|
||||||
|
tot += wear;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < 9; ++i) {
|
||||||
|
deciles[i] = 0;
|
||||||
|
decile_max[i] = (wmax * (i + 1) + 5) / 10;
|
||||||
|
}
|
||||||
|
deciles[9] = 0;
|
||||||
|
decile_max[9] = wmax;
|
||||||
|
for (i = 0; i < wear_eb_count; ++i) {
|
||||||
|
int d;
|
||||||
|
unsigned long wear = erase_block_wear[i];
|
||||||
|
for (d = 0; d < 10; ++d)
|
||||||
|
if (wear <= decile_max[d]) {
|
||||||
|
deciles[d] += 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
avg = tot / wear_eb_count;
|
||||||
|
|
||||||
|
/* Output wear report */
|
||||||
|
seq_printf(m, "Total numbers of erases: %lu\n", tot);
|
||||||
|
seq_printf(m, "Number of erase blocks: %u\n", wear_eb_count);
|
||||||
|
seq_printf(m, "Average number of erases: %lu\n", avg);
|
||||||
|
seq_printf(m, "Maximum number of erases: %lu\n", wmax);
|
||||||
|
seq_printf(m, "Minimum number of erases: %lu\n", wmin);
|
||||||
|
for (i = 0; i < 10; ++i) {
|
||||||
|
unsigned long from = (i ? decile_max[i - 1] + 1 : 0);
|
||||||
|
if (from > decile_max[i])
|
||||||
|
continue;
|
||||||
|
seq_printf(m, "Number of ebs with erase counts from %lu to %lu : %lu\n",
|
||||||
|
from,
|
||||||
|
decile_max[i],
|
||||||
|
deciles[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int nandsim_debugfs_open(struct inode *inode, struct file *file)
|
||||||
|
{
|
||||||
|
return single_open(file, nandsim_debugfs_show, inode->i_private);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct file_operations dfs_fops = {
|
||||||
|
.open = nandsim_debugfs_open,
|
||||||
|
.read = seq_read,
|
||||||
|
.llseek = seq_lseek,
|
||||||
|
.release = single_release,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* nandsim_debugfs_create - initialize debugfs
|
||||||
|
* @dev: nandsim device description object
|
||||||
|
*
|
||||||
|
* This function creates all debugfs files for UBI device @ubi. Returns zero in
|
||||||
|
* case of success and a negative error code in case of failure.
|
||||||
|
*/
|
||||||
|
static int nandsim_debugfs_create(struct nandsim *dev)
|
||||||
|
{
|
||||||
|
struct nandsim_debug_info *dbg = &dev->dbg;
|
||||||
|
struct dentry *dent;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
if (!IS_ENABLED(CONFIG_DEBUG_FS))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
dent = debugfs_create_dir("nandsim", NULL);
|
||||||
|
if (IS_ERR_OR_NULL(dent)) {
|
||||||
|
int err = dent ? -ENODEV : PTR_ERR(dent);
|
||||||
|
|
||||||
|
NS_ERR("cannot create \"nandsim\" debugfs directory, err %d\n",
|
||||||
|
err);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
dbg->dfs_root = dent;
|
||||||
|
|
||||||
|
dent = debugfs_create_file("wear_report", S_IRUSR,
|
||||||
|
dbg->dfs_root, dev, &dfs_fops);
|
||||||
|
if (IS_ERR_OR_NULL(dent))
|
||||||
|
goto out_remove;
|
||||||
|
dbg->dfs_wear_report = dent;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
out_remove:
|
||||||
|
debugfs_remove_recursive(dbg->dfs_root);
|
||||||
|
err = dent ? PTR_ERR(dent) : -ENODEV;
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* nandsim_debugfs_remove - destroy all debugfs files
|
||||||
|
*/
|
||||||
|
static void nandsim_debugfs_remove(struct nandsim *ns)
|
||||||
|
{
|
||||||
|
if (IS_ENABLED(CONFIG_DEBUG_FS))
|
||||||
|
debugfs_remove_recursive(ns->dbg.dfs_root);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Allocate array of page pointers, create slab allocation for an array
|
* Allocate array of page pointers, create slab allocation for an array
|
||||||
* and initialize the array by NULL pointers.
|
* and initialize the array by NULL pointers.
|
||||||
@ -911,8 +1029,6 @@ static int setup_wear_reporting(struct mtd_info *mtd)
|
|||||||
{
|
{
|
||||||
size_t mem;
|
size_t mem;
|
||||||
|
|
||||||
if (!rptwear)
|
|
||||||
return 0;
|
|
||||||
wear_eb_count = div_u64(mtd->size, mtd->erasesize);
|
wear_eb_count = div_u64(mtd->size, mtd->erasesize);
|
||||||
mem = wear_eb_count * sizeof(unsigned long);
|
mem = wear_eb_count * sizeof(unsigned long);
|
||||||
if (mem / sizeof(unsigned long) != wear_eb_count) {
|
if (mem / sizeof(unsigned long) != wear_eb_count) {
|
||||||
@ -929,64 +1045,18 @@ static int setup_wear_reporting(struct mtd_info *mtd)
|
|||||||
|
|
||||||
static void update_wear(unsigned int erase_block_no)
|
static void update_wear(unsigned int erase_block_no)
|
||||||
{
|
{
|
||||||
unsigned long wmin = -1, wmax = 0, avg;
|
|
||||||
unsigned long deciles[10], decile_max[10], tot = 0;
|
|
||||||
unsigned int i;
|
|
||||||
|
|
||||||
if (!erase_block_wear)
|
if (!erase_block_wear)
|
||||||
return;
|
return;
|
||||||
total_wear += 1;
|
total_wear += 1;
|
||||||
|
/*
|
||||||
|
* TODO: Notify this through a debugfs entry,
|
||||||
|
* instead of showing an error message.
|
||||||
|
*/
|
||||||
if (total_wear == 0)
|
if (total_wear == 0)
|
||||||
NS_ERR("Erase counter total overflow\n");
|
NS_ERR("Erase counter total overflow\n");
|
||||||
erase_block_wear[erase_block_no] += 1;
|
erase_block_wear[erase_block_no] += 1;
|
||||||
if (erase_block_wear[erase_block_no] == 0)
|
if (erase_block_wear[erase_block_no] == 0)
|
||||||
NS_ERR("Erase counter overflow for erase block %u\n", erase_block_no);
|
NS_ERR("Erase counter overflow for erase block %u\n", erase_block_no);
|
||||||
rptwear_cnt += 1;
|
|
||||||
if (rptwear_cnt < rptwear)
|
|
||||||
return;
|
|
||||||
rptwear_cnt = 0;
|
|
||||||
/* Calc wear stats */
|
|
||||||
for (i = 0; i < wear_eb_count; ++i) {
|
|
||||||
unsigned long wear = erase_block_wear[i];
|
|
||||||
if (wear < wmin)
|
|
||||||
wmin = wear;
|
|
||||||
if (wear > wmax)
|
|
||||||
wmax = wear;
|
|
||||||
tot += wear;
|
|
||||||
}
|
|
||||||
for (i = 0; i < 9; ++i) {
|
|
||||||
deciles[i] = 0;
|
|
||||||
decile_max[i] = (wmax * (i + 1) + 5) / 10;
|
|
||||||
}
|
|
||||||
deciles[9] = 0;
|
|
||||||
decile_max[9] = wmax;
|
|
||||||
for (i = 0; i < wear_eb_count; ++i) {
|
|
||||||
int d;
|
|
||||||
unsigned long wear = erase_block_wear[i];
|
|
||||||
for (d = 0; d < 10; ++d)
|
|
||||||
if (wear <= decile_max[d]) {
|
|
||||||
deciles[d] += 1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
avg = tot / wear_eb_count;
|
|
||||||
/* Output wear report */
|
|
||||||
NS_INFO("*** Wear Report ***\n");
|
|
||||||
NS_INFO("Total numbers of erases: %lu\n", tot);
|
|
||||||
NS_INFO("Number of erase blocks: %u\n", wear_eb_count);
|
|
||||||
NS_INFO("Average number of erases: %lu\n", avg);
|
|
||||||
NS_INFO("Maximum number of erases: %lu\n", wmax);
|
|
||||||
NS_INFO("Minimum number of erases: %lu\n", wmin);
|
|
||||||
for (i = 0; i < 10; ++i) {
|
|
||||||
unsigned long from = (i ? decile_max[i - 1] + 1 : 0);
|
|
||||||
if (from > decile_max[i])
|
|
||||||
continue;
|
|
||||||
NS_INFO("Number of ebs with erase counts from %lu to %lu : %lu\n",
|
|
||||||
from,
|
|
||||||
decile_max[i],
|
|
||||||
deciles[i]);
|
|
||||||
}
|
|
||||||
NS_INFO("*** End of Wear Report ***\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -2330,6 +2400,9 @@ static int __init ns_init_module(void)
|
|||||||
if ((retval = setup_wear_reporting(nsmtd)) != 0)
|
if ((retval = setup_wear_reporting(nsmtd)) != 0)
|
||||||
goto err_exit;
|
goto err_exit;
|
||||||
|
|
||||||
|
if ((retval = nandsim_debugfs_create(nand)) != 0)
|
||||||
|
goto err_exit;
|
||||||
|
|
||||||
if ((retval = init_nandsim(nsmtd)) != 0)
|
if ((retval = init_nandsim(nsmtd)) != 0)
|
||||||
goto err_exit;
|
goto err_exit;
|
||||||
|
|
||||||
@ -2369,6 +2442,7 @@ static void __exit ns_cleanup_module(void)
|
|||||||
struct nandsim *ns = ((struct nand_chip *)nsmtd->priv)->priv;
|
struct nandsim *ns = ((struct nand_chip *)nsmtd->priv)->priv;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
|
nandsim_debugfs_remove(ns);
|
||||||
free_nandsim(ns); /* Free nandsim private resources */
|
free_nandsim(ns); /* Free nandsim private resources */
|
||||||
nand_release(nsmtd); /* Unregister driver */
|
nand_release(nsmtd); /* Unregister driver */
|
||||||
for (i = 0;i < ARRAY_SIZE(ns->partitions); ++i)
|
for (i = 0;i < ARRAY_SIZE(ns->partitions); ++i)
|
||||||
|
Loading…
Reference in New Issue
Block a user