Add support for power-cut emulation on program and erase operations.

This power-cut emulation is configurable through the nandsim/powercut file
exposed in debugfs and can be triggered on erase or program operations.
The user can also specify a specific block and/or page (relative to a
block) at which he wants the power-cut to occur.

Here are some configuration examples:

disabled => the power-cut emulation is disabled
on-erase => power-cut emulation is active and triggered on all erase
            operations
on-erase:12 => power-cut emulation is active and triggered each time block
               12 is accessed
on-program => power-cut emulation is active and triggered each time a page
              is programmed
on-program::5 => power-cut emulation is active and triggered each time page
                 5 of any block is programmed
on-program:12:5 => power-cut emulation is active and triggered each time
                    page 5 of block 12 is programmed

Signed-off-by: Boris Brezillon <boris.brezil...@free-electrons.com>
---
 drivers/mtd/nand/nandsim.c | 172 ++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 162 insertions(+), 10 deletions(-)

diff --git a/drivers/mtd/nand/nandsim.c b/drivers/mtd/nand/nandsim.c
index efd8763..2f3f674 100644
--- a/drivers/mtd/nand/nandsim.c
+++ b/drivers/mtd/nand/nandsim.c
@@ -289,6 +289,7 @@ MODULE_PARM_DESC(bch,                "Enable BCH ecc and 
set how many bits should "
 struct nandsim_debug_info {
        struct dentry *dfs_root;
        struct dentry *dfs_wear_report;
+       struct dentry *dfs_powercut;
 };
 
 /*
@@ -299,6 +300,24 @@ union ns_mem {
        uint16_t *word;  /* for 16-bit word access */
 };
 
+enum ns_powercut_status {
+       NS_POWER_CUT_DISABLED,
+       NS_POWER_CUT_ON_ERASE,
+       NS_POWER_CUT_ON_PROGRAM,
+};
+
+struct ns_powercut {
+       enum ns_powercut_status status;
+       int block;
+       int page;
+};
+
+static struct ns_powercut powercut_info = {
+       .status = NS_POWER_CUT_DISABLED,
+       .block = -1,
+       .page = -1,
+};
+
 /*
  * The structure which describes all the internal simulator data.
  */
@@ -369,6 +388,7 @@ struct nandsim {
        void *file_buf;
        struct page *held_pages[NS_MAX_HELD_PAGES];
        int held_cnt;
+       bool powercut;
 
        struct nandsim_debug_info dbg;
 };
@@ -514,6 +534,78 @@ static const struct file_operations dfs_fops = {
        .release        = single_release,
 };
 
+static ssize_t read_file_powercut(struct file *file, char __user *user_buf,
+                                 size_t count, loff_t *ppos)
+{
+       char buf[32];
+       int size = 0;
+
+       switch (powercut_info.status) {
+       case NS_POWER_CUT_DISABLED:
+               size = snprintf(buf, sizeof(buf), "disabled\n");
+               break;
+       case NS_POWER_CUT_ON_ERASE:
+               size = snprintf(buf, sizeof(buf), "on-erase:%d\n",
+                               powercut_info.block);
+               break;
+       case NS_POWER_CUT_ON_PROGRAM:
+               size = snprintf(buf, sizeof(buf), "on-program:%d:%d\n",
+                               powercut_info.block, powercut_info.page);
+               break;
+       default:
+               break;
+       }
+
+       return simple_read_from_buffer(user_buf, size, ppos, buf, size);
+}
+
+static ssize_t write_file_powercut(struct file *file,
+                                  const char __user *user_buf,
+                                  size_t count, loff_t *ppos)
+{
+       char buf[32];
+       size_t buf_size;
+       char *ptr = buf;
+       long block = -1, page = -1;
+       int ret;
+
+       buf_size = min(count, (sizeof(buf) - 1));
+       if (copy_from_user(buf, user_buf, buf_size))
+               return -EFAULT;
+
+       buf[buf_size] = '\0';
+
+       if (!strncmp(buf, "disabled", sizeof("disabled") - 1)) {
+               powercut_info.status = NS_POWER_CUT_DISABLED;
+       } else if (!strncmp(buf, "on-erase", sizeof("on-erase") - 1)) {
+               strsep(&ptr, ":");
+               if (ptr)
+                       ret = kstrtol(ptr, 0, &block);
+               powercut_info.status = NS_POWER_CUT_ON_ERASE;
+       } else if (!strncmp(buf, "on-program", sizeof("on-program") - 1)) {
+               strsep(&ptr, ":");
+               if (ptr) {
+                       ret = kstrtol(ptr, 0, &block);
+                       strsep(&ptr, ":");
+                       if (ptr)
+                               ret = kstrtol(ptr, 0, &page);
+               }
+               powercut_info.status = NS_POWER_CUT_ON_PROGRAM;
+       }
+
+       powercut_info.block = block;
+       powercut_info.page = page;
+
+       return count;
+}
+
+static const struct file_operations powercut_fops = {
+       .read =         read_file_powercut,
+       .write =        write_file_powercut,
+       .open =         simple_open,
+       .llseek =       default_llseek,
+};
+
 /**
  * nandsim_debugfs_create - initialize debugfs
  * @dev: nandsim device description object
@@ -546,6 +638,12 @@ static int nandsim_debugfs_create(struct nandsim *dev)
                goto out_remove;
        dbg->dfs_wear_report = dent;
 
+       dent = debugfs_create_file("powercut", S_IWUSR | S_IRUSR,
+                                  dbg->dfs_root, dev, &powercut_fops);
+       if (IS_ERR_OR_NULL(dent))
+               goto out_remove;
+       dbg->dfs_powercut = dent;
+
        return 0;
 
 out_remove:
@@ -1206,6 +1304,10 @@ static inline void switch_to_ready_state(struct nandsim 
*ns, u_char status)
        ns->regs.row    = 0;
        ns->regs.column = 0;
        ns->regs.status = status;
+       if (ns->powercut) {
+               ns->powercut = false;
+               ns->regs.status |= NAND_STATUS_POWER_CUT;
+       }
 }
 
 /*
@@ -1509,29 +1611,60 @@ static void read_page(struct nandsim *ns, int num)
 /*
  * Erase all pages in the specified sector.
  */
-static void erase_sector(struct nandsim *ns)
+static int erase_sector(struct nandsim *ns)
 {
        union ns_mem *mypage;
        int i;
 
+       if (powercut_info.status == NS_POWER_CUT_ON_ERASE) {
+               long block = ns->regs.row / ns->geom.pgsec;
+
+               if (powercut_info.block < 0 ||
+                   powercut_info.block == block)
+                       ns->powercut = true;
+       }
+
        if (ns->cfile) {
-               for (i = 0; i < ns->geom.pgsec; i++)
-                       if (__test_and_clear_bit(ns->regs.row + i,
-                                                ns->pages_written)) {
-                               NS_DBG("erase_sector: freeing page %d\n", 
ns->regs.row + i);
+               for (i = 0; i < ns->geom.pgsec; i++) {
+                       if (ns->powercut) {
+                               loff_t pos = (loff_t)(ns->regs.row + i) *
+                                            ns->geom.pgszoob;
+
+                               /*
+                                * Corrupt all pages of the block if a
+                                * power-cut occurs in the middle of a
+                                * program operation.
+                                */
+                               prandom_bytes(ns->file_buf, ns->geom.pgszoob);
+                               write_file(ns, ns->cfile, ns->file_buf,
+                                          ns->geom.pgszoob, pos);
+                               set_bit(ns->regs.row + i, ns->pages_written);
+                               NS_DBG("erase_sector (power-cut emulation): 
corrupting page %d\n",
+                                      ns->regs.row + i);
+                       } else if (__test_and_clear_bit(ns->regs.row + i,
+                                                       ns->pages_written)) {
+                               NS_DBG("erase_sector: freeing page %d\n",
+                                      ns->regs.row + i);
                        }
-               return;
+               }
+
+               return ns->powercut ? -1 : 0;
        }
 
        mypage = NS_GET_PAGE(ns);
        for (i = 0; i < ns->geom.pgsec; i++) {
-               if (mypage->byte != NULL) {
+               if (ns->powercut) {
+                       if (mypage->byte)
+                               prandom_bytes(mypage->byte, ns->geom.pgszoob);
+               } else if (mypage->byte != NULL) {
                        NS_DBG("erase_sector: freeing page %d\n", 
ns->regs.row+i);
                        kmem_cache_free(ns->nand_pages_slab, mypage->byte);
                        mypage->byte = NULL;
                }
                mypage++;
        }
+
+       return ns->powercut ? -1 : 0;
 }
 
 /*
@@ -1543,6 +1676,24 @@ static int prog_page(struct nandsim *ns, int num)
        union ns_mem *mypage;
        u_char *pg_off;
 
+       if (powercut_info.status == NS_POWER_CUT_ON_PROGRAM) {
+               long block = ns->regs.row / ns->geom.pgsec;
+               long page = ns->regs.row % ns->geom.pgsec;
+
+               if (powercut_info.block < 0 ||
+                   powercut_info.block == block) {
+                       if (powercut_info.page < 0 ||
+                           powercut_info.page == page) {
+                               /*
+                                * Corrupt the page if a power-cut occurs in
+                                * the middle of a program operation.
+                                */
+                               prandom_bytes(ns->buf.byte, ns->geom.pgszoob);
+                               ns->powercut = true;
+                       }
+               }
+       }
+
        if (ns->cfile) {
                loff_t off;
                ssize_t tx;
@@ -1579,7 +1730,7 @@ static int prog_page(struct nandsim *ns, int num)
                                return -1;
                        }
                }
-               return 0;
+               return ns->powercut ? -1 : 0;
        }
 
        mypage = NS_GET_PAGE(ns);
@@ -1603,7 +1754,7 @@ static int prog_page(struct nandsim *ns, int num)
        for (i = 0; i < num; i++)
                pg_off[i] &= ns->buf.byte[i];
 
-       return 0;
+       return ns->powercut ? -1 : 0;
 }
 
 /*
@@ -1681,7 +1832,8 @@ static int do_state_action(struct nandsim *ns, uint32_t 
action)
                                ns->regs.row, NS_RAW_OFFSET(ns));
                NS_LOG("erase sector %u\n", erase_block_no);
 
-               erase_sector(ns);
+               if (erase_sector(ns) == -1)
+                       return -1;
 
                NS_MDELAY(erase_delay);
 
-- 
1.9.1

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Reply via email to