[MTD] nandsim: enhance nandsim to simulate flash errors
[pandora-kernel.git] / drivers / mtd / nand / nandsim.c
index 5dd3c4e..05b4207 100644 (file)
@@ -37,6 +37,8 @@
 #include <linux/mtd/nand.h>
 #include <linux/mtd/partitions.h>
 #include <linux/delay.h>
+#include <linux/list.h>
+#include <linux/random.h>
 
 /* Default simulator parameters values */
 #if !defined(CONFIG_NANDSIM_FIRST_ID_BYTE)  || \
@@ -90,6 +92,13 @@ static uint bus_width      = CONFIG_NANDSIM_BUS_WIDTH;
 static uint do_delays      = CONFIG_NANDSIM_DO_DELAYS;
 static uint log            = CONFIG_NANDSIM_LOG;
 static uint dbg            = CONFIG_NANDSIM_DBG;
+static unsigned long parts[MAX_MTD_DEVICES];
+static unsigned int parts_num;
+static char *badblocks = NULL;
+static char *weakblocks = NULL;
+static char *weakpages = NULL;
+static unsigned int bitflips = 0;
+static char *gravepages = NULL;
 
 module_param(first_id_byte,  uint, 0400);
 module_param(second_id_byte, uint, 0400);
@@ -104,6 +113,12 @@ module_param(bus_width,      uint, 0400);
 module_param(do_delays,      uint, 0400);
 module_param(log,            uint, 0400);
 module_param(dbg,            uint, 0400);
+module_param_array(parts, ulong, &parts_num, 0400);
+module_param(badblocks,      charp, 0400);
+module_param(weakblocks,     charp, 0400);
+module_param(weakpages,      charp, 0400);
+module_param(bitflips,       uint, 0400);
+module_param(gravepages,     charp, 0400);
 
 MODULE_PARM_DESC(first_id_byte,  "The fist byte returned by NAND Flash 'read ID' command (manufaturer ID)");
 MODULE_PARM_DESC(second_id_byte, "The second byte returned by NAND Flash 'read ID' command (chip ID)");
@@ -118,6 +133,19 @@ MODULE_PARM_DESC(bus_width,      "Chip's bus width (8- or 16-bit)");
 MODULE_PARM_DESC(do_delays,      "Simulate NAND delays using busy-waits if not zero");
 MODULE_PARM_DESC(log,            "Perform logging if not zero");
 MODULE_PARM_DESC(dbg,            "Output debug information if not zero");
+MODULE_PARM_DESC(parts,          "Partition sizes (in erase blocks) separated by commas");
+/* Page and erase block positions for the following parameters are independent of any partitions */
+MODULE_PARM_DESC(badblocks,      "Erase blocks that are initially marked bad, separated by commas");
+MODULE_PARM_DESC(weakblocks,     "Weak erase blocks [: remaining erase cycles (defaults to 3)]"
+                                " separated by commas e.g. 113:2 means eb 113"
+                                " can be erased only twice before failing");
+MODULE_PARM_DESC(weakpages,      "Weak pages [: maximum writes (defaults to 3)]"
+                                " separated by commas e.g. 1401:2 means page 1401"
+                                " can be written only twice before failing");
+MODULE_PARM_DESC(bitflips,       "Maximum number of random bit flips per page (zero by default)");
+MODULE_PARM_DESC(gravepages,     "Pages that lose data [: maximum reads (defaults to 3)]"
+                                " separated by commas e.g. 1401:2 means page 1401"
+                                " can be read only twice before failing");
 
 /* The largest possible page size */
 #define NS_LARGEST_PAGE_SIZE   2048
@@ -131,9 +159,9 @@ MODULE_PARM_DESC(dbg,            "Output debug information if not zero");
 #define NS_DBG(args...) \
        do { if (dbg) printk(KERN_DEBUG NS_OUTPUT_PREFIX " debug: " args); } while(0)
 #define NS_WARN(args...) \
-       do { printk(KERN_WARNING NS_OUTPUT_PREFIX " warnig: " args); } while(0)
+       do { printk(KERN_WARNING NS_OUTPUT_PREFIX " warning: " args); } while(0)
 #define NS_ERR(args...) \
-       do { printk(KERN_ERR NS_OUTPUT_PREFIX " errorr: " args); } while(0)
+       do { printk(KERN_ERR NS_OUTPUT_PREFIX " error: " args); } while(0)
 
 /* Busy-wait delay macros (microseconds, milliseconds) */
 #define NS_UDELAY(us) \
@@ -160,7 +188,7 @@ MODULE_PARM_DESC(dbg,            "Output debug information if not zero");
 /* After a command is input, the simulator goes to one of the following states */
 #define STATE_CMD_READ0        0x00000001 /* read data from the beginning of page */
 #define STATE_CMD_READ1        0x00000002 /* read data from the second half of page */
-#define STATE_CMD_READSTART      0x00000003 /* read data second command (large page devices) */
+#define STATE_CMD_READSTART    0x00000003 /* read data second command (large page devices) */
 #define STATE_CMD_PAGEPROG     0x00000004 /* start page programm */
 #define STATE_CMD_READOOB      0x00000005 /* read OOB area */
 #define STATE_CMD_ERASE1       0x00000006 /* sector erase first command */
@@ -226,11 +254,20 @@ MODULE_PARM_DESC(dbg,            "Output debug information if not zero");
  */
 #define NS_MAX_PREVSTATES 1
 
+/*
+ * A union to represent flash memory contents and flash buffer.
+ */
+union ns_mem {
+       u_char *byte;    /* for byte access */
+       uint16_t *word;  /* for 16-bit word access */
+};
+
 /*
  * The structure which describes all the internal simulator data.
  */
 struct nandsim {
-       struct mtd_partition part;
+       struct mtd_partition partitions[MAX_MTD_DEVICES];
+       unsigned int nbparts;
 
        uint busw;              /* flash chip bus width (8 or 16) */
        u_char ids[4];          /* chip's ID bytes */
@@ -243,17 +280,11 @@ struct nandsim {
        uint16_t npstates;      /* number of previous states saved */
        uint16_t stateidx;      /* current state index */
 
-       /* The simulated NAND flash image */
-       union flash_media {
-               u_char *byte;
-               uint16_t    *word;
-       } mem;
+       /* The simulated NAND flash pages array */
+       union ns_mem *pages;
 
        /* Internal buffer of page + OOB size bytes */
-       union internal_buffer {
-               u_char *byte;    /* for byte access */
-               uint16_t *word;  /* for 16-bit word access */
-       } buf;
+       union ns_mem buf;
 
        /* NAND flash "geometry" */
        struct nandsin_geometry {
@@ -336,22 +367,95 @@ static struct nandsim_operations {
                               STATE_DATAOUT, STATE_READY}}
 };
 
+struct weak_block {
+       struct list_head list;
+       unsigned int erase_block_no;
+       unsigned int max_erases;
+       unsigned int erases_done;
+};
+
+static LIST_HEAD(weak_blocks);
+
+struct weak_page {
+       struct list_head list;
+       unsigned int page_no;
+       unsigned int max_writes;
+       unsigned int writes_done;
+};
+
+static LIST_HEAD(weak_pages);
+
+struct grave_page {
+       struct list_head list;
+       unsigned int page_no;
+       unsigned int max_reads;
+       unsigned int reads_done;
+};
+
+static LIST_HEAD(grave_pages);
+
 /* MTD structure for NAND controller */
 static struct mtd_info *nsmtd;
 
 static u_char ns_verify_buf[NS_LARGEST_PAGE_SIZE];
 
+/*
+ * Allocate array of page pointers and initialize the array to NULL
+ * pointers.
+ *
+ * RETURNS: 0 if success, -ENOMEM if memory alloc fails.
+ */
+static int alloc_device(struct nandsim *ns)
+{
+       int i;
+
+       ns->pages = vmalloc(ns->geom.pgnum * sizeof(union ns_mem));
+       if (!ns->pages) {
+               NS_ERR("alloc_map: unable to allocate page array\n");
+               return -ENOMEM;
+       }
+       for (i = 0; i < ns->geom.pgnum; i++) {
+               ns->pages[i].byte = NULL;
+       }
+
+       return 0;
+}
+
+/*
+ * Free any allocated pages, and free the array of page pointers.
+ */
+static void free_device(struct nandsim *ns)
+{
+       int i;
+
+       if (ns->pages) {
+               for (i = 0; i < ns->geom.pgnum; i++) {
+                       if (ns->pages[i].byte)
+                               kfree(ns->pages[i].byte);
+               }
+               vfree(ns->pages);
+       }
+}
+
+static char *get_partition_name(int i)
+{
+       char buf[64];
+       sprintf(buf, "NAND simulator partition %d", i);
+       return kstrdup(buf, GFP_KERNEL);
+}
+
 /*
  * Initialize the nandsim structure.
  *
  * RETURNS: 0 if success, -ERRNO if failure.
  */
-static int
-init_nandsim(struct mtd_info *mtd)
+static int init_nandsim(struct mtd_info *mtd)
 {
        struct nand_chip *chip = (struct nand_chip *)mtd->priv;
        struct nandsim   *ns   = (struct nandsim *)(chip->priv);
-       int i;
+       int i, ret = 0;
+       u_int32_t remains;
+       u_int32_t next_offset;
 
        if (NS_IS_INITIALIZED(ns)) {
                NS_ERR("init_nandsim: nandsim is already initialized\n");
@@ -401,7 +505,7 @@ init_nandsim(struct mtd_info *mtd)
                }
        } else {
                if (ns->geom.totsz <= (128 << 20)) {
-                       ns->geom.pgaddrbytes  = 5;
+                       ns->geom.pgaddrbytes  = 4;
                        ns->geom.secaddrbytes = 2;
                } else {
                        ns->geom.pgaddrbytes  = 5;
@@ -409,6 +513,40 @@ init_nandsim(struct mtd_info *mtd)
                }
        }
 
+       /* Fill the partition_info structure */
+       if (parts_num > ARRAY_SIZE(ns->partitions)) {
+               NS_ERR("too many partitions.\n");
+               ret = -EINVAL;
+               goto error;
+       }
+       remains = ns->geom.totsz;
+       next_offset = 0;
+       for (i = 0; i < parts_num; ++i) {
+               unsigned long part = parts[i];
+               if (!part || part > remains / ns->geom.secsz) {
+                       NS_ERR("bad partition size.\n");
+                       ret = -EINVAL;
+                       goto error;
+               }
+               ns->partitions[i].name   = get_partition_name(i);
+               ns->partitions[i].offset = next_offset;
+               ns->partitions[i].size   = part * ns->geom.secsz;
+               next_offset += ns->partitions[i].size;
+               remains -= ns->partitions[i].size;
+       }
+       ns->nbparts = parts_num;
+       if (remains) {
+               if (parts_num + 1 > ARRAY_SIZE(ns->partitions)) {
+                       NS_ERR("too many partitions.\n");
+                       ret = -EINVAL;
+                       goto error;
+               }
+               ns->partitions[i].name   = get_partition_name(i);
+               ns->partitions[i].offset = next_offset;
+               ns->partitions[i].size   = remains;
+               ns->nbparts += 1;
+       }
+
        /* Detect how many ID bytes the NAND chip outputs */
         for (i = 0; nand_flash_ids[i].name != NULL; i++) {
                 if (second_id_byte != nand_flash_ids[i].id)
@@ -435,54 +573,240 @@ init_nandsim(struct mtd_info *mtd)
        printk("sector address bytes: %u\n",    ns->geom.secaddrbytes);
        printk("options: %#x\n",                ns->options);
 
-       /* Map / allocate and initialize the flash image */
-       ns->mem.byte = vmalloc(ns->geom.totszoob);
-       if (!ns->mem.byte) {
-               NS_ERR("init_nandsim: unable to allocate %u bytes for flash image\n",
-                       ns->geom.totszoob);
-               return -ENOMEM;
-       }
-       memset(ns->mem.byte, 0xFF, ns->geom.totszoob);
+       if ((ret = alloc_device(ns)) != 0)
+               goto error;
 
        /* Allocate / initialize the internal buffer */
        ns->buf.byte = kmalloc(ns->geom.pgszoob, GFP_KERNEL);
        if (!ns->buf.byte) {
                NS_ERR("init_nandsim: unable to allocate %u bytes for the internal buffer\n",
                        ns->geom.pgszoob);
+               ret = -ENOMEM;
                goto error;
        }
        memset(ns->buf.byte, 0xFF, ns->geom.pgszoob);
 
-       /* Fill the partition_info structure */
-       ns->part.name   = "NAND simulator partition";
-       ns->part.offset = 0;
-       ns->part.size   = ns->geom.totsz;
-
        return 0;
 
 error:
-       vfree(ns->mem.byte);
+       free_device(ns);
 
-       return -ENOMEM;
+       return ret;
 }
 
 /*
  * Free the nandsim structure.
  */
-static void
-free_nandsim(struct nandsim *ns)
+static void free_nandsim(struct nandsim *ns)
 {
        kfree(ns->buf.byte);
-       vfree(ns->mem.byte);
+       free_device(ns);
 
        return;
 }
 
+static int parse_badblocks(struct nandsim *ns, struct mtd_info *mtd)
+{
+       char *w;
+       int zero_ok;
+       unsigned int erase_block_no;
+       loff_t offset;
+
+       if (!badblocks)
+               return 0;
+       w = badblocks;
+       do {
+               zero_ok = (*w == '0' ? 1 : 0);
+               erase_block_no = simple_strtoul(w, &w, 0);
+               if (!zero_ok && !erase_block_no) {
+                       NS_ERR("invalid badblocks.\n");
+                       return -EINVAL;
+               }
+               offset = erase_block_no * ns->geom.secsz;
+               if (mtd->block_markbad(mtd, offset)) {
+                       NS_ERR("invalid badblocks.\n");
+                       return -EINVAL;
+               }
+               if (*w == ',')
+                       w += 1;
+       } while (*w);
+       return 0;
+}
+
+static int parse_weakblocks(void)
+{
+       char *w;
+       int zero_ok;
+       unsigned int erase_block_no;
+       unsigned int max_erases;
+       struct weak_block *wb;
+
+       if (!weakblocks)
+               return 0;
+       w = weakblocks;
+       do {
+               zero_ok = (*w == '0' ? 1 : 0);
+               erase_block_no = simple_strtoul(w, &w, 0);
+               if (!zero_ok && !erase_block_no) {
+                       NS_ERR("invalid weakblocks.\n");
+                       return -EINVAL;
+               }
+               max_erases = 3;
+               if (*w == ':') {
+                       w += 1;
+                       max_erases = simple_strtoul(w, &w, 0);
+               }
+               if (*w == ',')
+                       w += 1;
+               wb = kzalloc(sizeof(*wb), GFP_KERNEL);
+               if (!wb) {
+                       NS_ERR("unable to allocate memory.\n");
+                       return -ENOMEM;
+               }
+               wb->erase_block_no = erase_block_no;
+               wb->max_erases = max_erases;
+               list_add(&wb->list, &weak_blocks);
+       } while (*w);
+       return 0;
+}
+
+static int erase_error(unsigned int erase_block_no)
+{
+       struct weak_block *wb;
+
+       list_for_each_entry(wb, &weak_blocks, list)
+               if (wb->erase_block_no == erase_block_no) {
+                       if (wb->erases_done >= wb->max_erases)
+                               return 1;
+                       wb->erases_done += 1;
+                       return 0;
+               }
+       return 0;
+}
+
+static int parse_weakpages(void)
+{
+       char *w;
+       int zero_ok;
+       unsigned int page_no;
+       unsigned int max_writes;
+       struct weak_page *wp;
+
+       if (!weakpages)
+               return 0;
+       w = weakpages;
+       do {
+               zero_ok = (*w == '0' ? 1 : 0);
+               page_no = simple_strtoul(w, &w, 0);
+               if (!zero_ok && !page_no) {
+                       NS_ERR("invalid weakpagess.\n");
+                       return -EINVAL;
+               }
+               max_writes = 3;
+               if (*w == ':') {
+                       w += 1;
+                       max_writes = simple_strtoul(w, &w, 0);
+               }
+               if (*w == ',')
+                       w += 1;
+               wp = kzalloc(sizeof(*wp), GFP_KERNEL);
+               if (!wp) {
+                       NS_ERR("unable to allocate memory.\n");
+                       return -ENOMEM;
+               }
+               wp->page_no = page_no;
+               wp->max_writes = max_writes;
+               list_add(&wp->list, &weak_pages);
+       } while (*w);
+       return 0;
+}
+
+static int write_error(unsigned int page_no)
+{
+       struct weak_page *wp;
+
+       list_for_each_entry(wp, &weak_pages, list)
+               if (wp->page_no == page_no) {
+                       if (wp->writes_done >= wp->max_writes)
+                               return 1;
+                       wp->writes_done += 1;
+                       return 0;
+               }
+       return 0;
+}
+
+static int parse_gravepages(void)
+{
+       char *g;
+       int zero_ok;
+       unsigned int page_no;
+       unsigned int max_reads;
+       struct grave_page *gp;
+
+       if (!gravepages)
+               return 0;
+       g = gravepages;
+       do {
+               zero_ok = (*g == '0' ? 1 : 0);
+               page_no = simple_strtoul(g, &g, 0);
+               if (!zero_ok && !page_no) {
+                       NS_ERR("invalid gravepagess.\n");
+                       return -EINVAL;
+               }
+               max_reads = 3;
+               if (*g == ':') {
+                       g += 1;
+                       max_reads = simple_strtoul(g, &g, 0);
+               }
+               if (*g == ',')
+                       g += 1;
+               gp = kzalloc(sizeof(*gp), GFP_KERNEL);
+               if (!gp) {
+                       NS_ERR("unable to allocate memory.\n");
+                       return -ENOMEM;
+               }
+               gp->page_no = page_no;
+               gp->max_reads = max_reads;
+               list_add(&gp->list, &grave_pages);
+       } while (*g);
+       return 0;
+}
+
+static int read_error(unsigned int page_no)
+{
+       struct grave_page *gp;
+
+       list_for_each_entry(gp, &grave_pages, list)
+               if (gp->page_no == page_no) {
+                       if (gp->reads_done >= gp->max_reads)
+                               return 1;
+                       gp->reads_done += 1;
+                       return 0;
+               }
+       return 0;
+}
+
+static void free_lists(void)
+{
+       struct list_head *pos, *n;
+       list_for_each_safe(pos, n, &weak_blocks) {
+               list_del(pos);
+               kfree(list_entry(pos, struct weak_block, list));
+       }
+       list_for_each_safe(pos, n, &weak_pages) {
+               list_del(pos);
+               kfree(list_entry(pos, struct weak_page, list));
+       }
+       list_for_each_safe(pos, n, &grave_pages) {
+               list_del(pos);
+               kfree(list_entry(pos, struct grave_page, list));
+       }
+}
+
 /*
  * Returns the string representation of 'state' state.
  */
-static char *
-get_state_name(uint32_t state)
+static char *get_state_name(uint32_t state)
 {
        switch (NS_STATE(state)) {
                case STATE_CMD_READ0:
@@ -540,8 +864,7 @@ get_state_name(uint32_t state)
  *
  * RETURNS: 1 if wrong command, 0 if right.
  */
-static int
-check_command(int cmd)
+static int check_command(int cmd)
 {
        switch (cmd) {
 
@@ -567,8 +890,7 @@ check_command(int cmd)
 /*
  * Returns state after command is accepted by command number.
  */
-static uint32_t
-get_state_by_command(unsigned command)
+static uint32_t get_state_by_command(unsigned command)
 {
        switch (command) {
                case NAND_CMD_READ0:
@@ -604,8 +926,7 @@ get_state_by_command(unsigned command)
 /*
  * Move an address byte to the correspondent internal register.
  */
-static inline void
-accept_addr_byte(struct nandsim *ns, u_char bt)
+static inline void accept_addr_byte(struct nandsim *ns, u_char bt)
 {
        uint byte = (uint)bt;
 
@@ -623,8 +944,7 @@ accept_addr_byte(struct nandsim *ns, u_char bt)
 /*
  * Switch to STATE_READY state.
  */
-static inline void
-switch_to_ready_state(struct nandsim *ns, u_char status)
+static inline void switch_to_ready_state(struct nandsim *ns, u_char status)
 {
        NS_DBG("switch_to_ready_state: switch to %s state\n", get_state_name(STATE_READY));
 
@@ -683,8 +1003,7 @@ switch_to_ready_state(struct nandsim *ns, u_char status)
  *          -1 - several matches.
  *           0 - operation is found.
  */
-static int
-find_operation(struct nandsim *ns, uint32_t flag)
+static int find_operation(struct nandsim *ns, uint32_t flag)
 {
        int opsfound = 0;
        int i, j, idx = 0;
@@ -768,16 +1087,118 @@ find_operation(struct nandsim *ns, uint32_t flag)
        return -1;
 }
 
+/*
+ * Returns a pointer to the current page.
+ */
+static inline union ns_mem *NS_GET_PAGE(struct nandsim *ns)
+{
+       return &(ns->pages[ns->regs.row]);
+}
+
+/*
+ * Retuns a pointer to the current byte, within the current page.
+ */
+static inline u_char *NS_PAGE_BYTE_OFF(struct nandsim *ns)
+{
+       return NS_GET_PAGE(ns)->byte + ns->regs.column + ns->regs.off;
+}
+
+/*
+ * Fill the NAND buffer with data read from the specified page.
+ */
+static void read_page(struct nandsim *ns, int num)
+{
+       union ns_mem *mypage;
+
+       mypage = NS_GET_PAGE(ns);
+       if (mypage->byte == NULL) {
+               NS_DBG("read_page: page %d not allocated\n", ns->regs.row);
+               memset(ns->buf.byte, 0xFF, num);
+       } else {
+               unsigned int page_no = ns->regs.row;
+               NS_DBG("read_page: page %d allocated, reading from %d\n",
+                       ns->regs.row, ns->regs.column + ns->regs.off);
+               if (read_error(page_no)) {
+                       int i;
+                       memset(ns->buf.byte, 0xFF, num);
+                       for (i = 0; i < num; ++i)
+                               ns->buf.byte[i] = random32();
+                       NS_WARN("simulating read error in page %u\n", page_no);
+                       return;
+               }
+               memcpy(ns->buf.byte, NS_PAGE_BYTE_OFF(ns), num);
+               if (bitflips && random32() < (1 << 22)) {
+                       int flips = 1;
+                       if (bitflips > 1)
+                               flips = (random32() % (int) bitflips) + 1;
+                       while (flips--) {
+                               int pos = random32() % (num * 8);
+                               ns->buf.byte[pos / 8] ^= (1 << (pos % 8));
+                               NS_WARN("read_page: flipping bit %d in page %d "
+                                       "reading from %d ecc: corrected=%u failed=%u\n",
+                                       pos, ns->regs.row, ns->regs.column + ns->regs.off,
+                                       nsmtd->ecc_stats.corrected, nsmtd->ecc_stats.failed);
+                       }
+               }
+       }
+}
+
+/*
+ * Erase all pages in the specified sector.
+ */
+static void erase_sector(struct nandsim *ns)
+{
+       union ns_mem *mypage;
+       int i;
+
+       mypage = NS_GET_PAGE(ns);
+       for (i = 0; i < ns->geom.pgsec; i++) {
+               if (mypage->byte != NULL) {
+                       NS_DBG("erase_sector: freeing page %d\n", ns->regs.row+i);
+                       kfree(mypage->byte);
+                       mypage->byte = NULL;
+               }
+               mypage++;
+       }
+}
+
+/*
+ * Program the specified page with the contents from the NAND buffer.
+ */
+static int prog_page(struct nandsim *ns, int num)
+{
+       int i;
+       union ns_mem *mypage;
+       u_char *pg_off;
+
+       mypage = NS_GET_PAGE(ns);
+       if (mypage->byte == NULL) {
+               NS_DBG("prog_page: allocating page %d\n", ns->regs.row);
+               mypage->byte = kmalloc(ns->geom.pgszoob, GFP_KERNEL);
+               if (mypage->byte == NULL) {
+                       NS_ERR("prog_page: error allocating memory for page %d\n", ns->regs.row);
+                       return -1;
+               }
+               memset(mypage->byte, 0xFF, ns->geom.pgszoob);
+       }
+
+       pg_off = NS_PAGE_BYTE_OFF(ns);
+       for (i = 0; i < num; i++)
+               pg_off[i] &= ns->buf.byte[i];
+
+       return 0;
+}
+
 /*
  * If state has any action bit, perform this action.
  *
  * RETURNS: 0 if success, -1 if error.
  */
-static int
-do_state_action(struct nandsim *ns, uint32_t action)
+static int do_state_action(struct nandsim *ns, uint32_t action)
 {
-       int i, num;
+       int num;
        int busdiv = ns->busw == 8 ? 1 : 2;
+       unsigned int erase_block_no, page_no;
 
        action &= ACTION_MASK;
 
@@ -800,7 +1221,7 @@ do_state_action(struct nandsim *ns, uint32_t action)
                        break;
                }
                num = ns->geom.pgszoob - ns->regs.off - ns->regs.column;
-               memcpy(ns->buf.byte, ns->mem.byte + NS_RAW_OFFSET(ns) + ns->regs.off, num);
+               read_page(ns, num);
 
                NS_DBG("do_state_action: (ACTION_CPY:) copy %d bytes to int buf, raw offset %d\n",
                        num, NS_RAW_OFFSET(ns) + ns->regs.off);
@@ -837,14 +1258,21 @@ do_state_action(struct nandsim *ns, uint32_t action)
                                8 * (ns->geom.pgaddrbytes - ns->geom.secaddrbytes)) | ns->regs.column;
                ns->regs.column = 0;
 
+               erase_block_no = ns->regs.row >> (ns->geom.secshift - ns->geom.pgshift);
+
                NS_DBG("do_state_action: erase sector at address %#x, off = %d\n",
                                ns->regs.row, NS_RAW_OFFSET(ns));
-               NS_LOG("erase sector %d\n", ns->regs.row >> (ns->geom.secshift - ns->geom.pgshift));
+               NS_LOG("erase sector %u\n", erase_block_no);
 
-               memset(ns->mem.byte + NS_RAW_OFFSET(ns), 0xFF, ns->geom.secszoob);
+               erase_sector(ns);
 
                NS_MDELAY(erase_delay);
 
+               if (erase_error(erase_block_no)) {
+                       NS_WARN("simulating erase failure in erase block %u\n", erase_block_no);
+                       return -1;
+               }
+
                break;
 
        case ACTION_PRGPAGE:
@@ -864,8 +1292,10 @@ do_state_action(struct nandsim *ns, uint32_t action)
                        return -1;
                }
 
-               for (i = 0; i < num; i++)
-                       ns->mem.byte[NS_RAW_OFFSET(ns) + ns->regs.off + i] &= ns->buf.byte[i];
+               if (prog_page(ns, num) == -1)
+                       return -1;
+
+               page_no = ns->regs.row;
 
                NS_DBG("do_state_action: copy %d bytes from int buf to (%#x, %#x), raw off = %d\n",
                        num, ns->regs.row, ns->regs.column, NS_RAW_OFFSET(ns) + ns->regs.off);
@@ -874,6 +1304,11 @@ do_state_action(struct nandsim *ns, uint32_t action)
                NS_UDELAY(programm_delay);
                NS_UDELAY(output_cycle * ns->geom.pgsz / 1000 / busdiv);
 
+               if (write_error(page_no)) {
+                       NS_WARN("simulating write failure in page %u\n", page_no);
+                       return -1;
+               }
+
                break;
 
        case ACTION_ZEROOFF:
@@ -906,8 +1341,7 @@ do_state_action(struct nandsim *ns, uint32_t action)
 /*
  * Switch simulator's state.
  */
-static void
-switch_state(struct nandsim *ns)
+static void switch_state(struct nandsim *ns)
 {
        if (ns->op) {
                /*
@@ -1048,8 +1482,7 @@ switch_state(struct nandsim *ns)
        }
 }
 
-static u_char
-ns_nand_read_byte(struct mtd_info *mtd)
+static u_char ns_nand_read_byte(struct mtd_info *mtd)
 {
         struct nandsim *ns = (struct nandsim *)((struct nand_chip *)mtd->priv)->priv;
        u_char outb = 0x00;
@@ -1122,8 +1555,7 @@ ns_nand_read_byte(struct mtd_info *mtd)
        return outb;
 }
 
-static void
-ns_nand_write_byte(struct mtd_info *mtd, u_char byte)
+static void ns_nand_write_byte(struct mtd_info *mtd, u_char byte)
 {
         struct nandsim *ns = (struct nandsim *)((struct nand_chip *)mtd->priv)->priv;
 
@@ -1286,15 +1718,13 @@ static void ns_hwcontrol(struct mtd_info *mtd, int cmd, unsigned int bitmask)
                ns_nand_write_byte(mtd, cmd);
 }
 
-static int
-ns_device_ready(struct mtd_info *mtd)
+static int ns_device_ready(struct mtd_info *mtd)
 {
        NS_DBG("device_ready\n");
        return 1;
 }
 
-static uint16_t
-ns_nand_read_word(struct mtd_info *mtd)
+static uint16_t ns_nand_read_word(struct mtd_info *mtd)
 {
        struct nand_chip *chip = (struct nand_chip *)mtd->priv;
 
@@ -1303,8 +1733,7 @@ ns_nand_read_word(struct mtd_info *mtd)
        return chip->read_byte(mtd) | (chip->read_byte(mtd) << 8);
 }
 
-static void
-ns_nand_write_buf(struct mtd_info *mtd, const u_char *buf, int len)
+static void ns_nand_write_buf(struct mtd_info *mtd, const u_char *buf, int len)
 {
         struct nandsim *ns = (struct nandsim *)((struct nand_chip *)mtd->priv)->priv;
 
@@ -1331,8 +1760,7 @@ ns_nand_write_buf(struct mtd_info *mtd, const u_char *buf, int len)
        }
 }
 
-static void
-ns_nand_read_buf(struct mtd_info *mtd, u_char *buf, int len)
+static void ns_nand_read_buf(struct mtd_info *mtd, u_char *buf, int len)
 {
         struct nandsim *ns = (struct nandsim *)((struct nand_chip *)mtd->priv)->priv;
 
@@ -1385,8 +1813,7 @@ ns_nand_read_buf(struct mtd_info *mtd, u_char *buf, int len)
        return;
 }
 
-static int
-ns_nand_verify_buf(struct mtd_info *mtd, const u_char *buf, int len)
+static int ns_nand_verify_buf(struct mtd_info *mtd, const u_char *buf, int len)
 {
        ns_nand_read_buf(mtd, (u_char *)&ns_verify_buf[0], len);
 
@@ -1406,7 +1833,7 @@ static int __init ns_init_module(void)
 {
        struct nand_chip *chip;
        struct nandsim *nand;
-       int retval = -ENOMEM;
+       int retval = -ENOMEM, i;
 
        if (bus_width != 8 && bus_width != 16) {
                NS_ERR("wrong bus width (%d), use only 8 or 16\n", bus_width);
@@ -1414,14 +1841,12 @@ static int __init ns_init_module(void)
        }
 
        /* Allocate and initialize mtd_info, nand_chip and nandsim structures */
-       nsmtd = kmalloc(sizeof(struct mtd_info) + sizeof(struct nand_chip)
+       nsmtd = kzalloc(sizeof(struct mtd_info) + sizeof(struct nand_chip)
                                + sizeof(struct nandsim), GFP_KERNEL);
        if (!nsmtd) {
                NS_ERR("unable to allocate core structures.\n");
                return -ENOMEM;
        }
-       memset(nsmtd, 0, sizeof(struct mtd_info) + sizeof(struct nand_chip) +
-                       sizeof(struct nandsim));
        chip        = (struct nand_chip *)(nsmtd + 1);
         nsmtd->priv = (void *)chip;
        nand        = (struct nandsim *)(chip + 1);
@@ -1462,6 +1887,15 @@ static int __init ns_init_module(void)
 
        nsmtd->owner = THIS_MODULE;
 
+       if ((retval = parse_weakblocks()) != 0)
+               goto error;
+
+       if ((retval = parse_weakpages()) != 0)
+               goto error;
+
+       if ((retval = parse_gravepages()) != 0)
+               goto error;
+
        if ((retval = nand_scan(nsmtd, 1)) != 0) {
                NS_ERR("can't register NAND Simulator\n");
                if (retval > 0)
@@ -1469,23 +1903,29 @@ static int __init ns_init_module(void)
                goto error;
        }
 
-       if ((retval = init_nandsim(nsmtd)) != 0) {
-               NS_ERR("scan_bbt: can't initialize the nandsim structure\n");
-               goto error;
-       }
+       if ((retval = init_nandsim(nsmtd)) != 0)
+               goto err_exit;
 
-       if ((retval = nand_default_bbt(nsmtd)) != 0) {
-               free_nandsim(nand);
-               goto error;
-       }
+       if ((retval = parse_badblocks(nand, nsmtd)) != 0)
+               goto err_exit;
+
+       if ((retval = nand_default_bbt(nsmtd)) != 0)
+               goto err_exit;
 
-       /* Register NAND as one big partition */
-       add_mtd_partitions(nsmtd, &nand->part, 1);
+       /* Register NAND partitions */
+       if ((retval = add_mtd_partitions(nsmtd, &nand->partitions[0], nand->nbparts)) != 0)
+               goto err_exit;
 
         return 0;
 
+err_exit:
+       free_nandsim(nand);
+       nand_release(nsmtd);
+       for (i = 0;i < ARRAY_SIZE(nand->partitions); ++i)
+               kfree(nand->partitions[i].name);
 error:
        kfree(nsmtd);
+       free_lists();
 
        return retval;
 }
@@ -1498,10 +1938,14 @@ module_init(ns_init_module);
 static void __exit ns_cleanup_module(void)
 {
        struct nandsim *ns = (struct nandsim *)(((struct nand_chip *)nsmtd->priv)->priv);
+       int i;
 
        free_nandsim(ns);    /* Free nandsim private resources */
-       nand_release(nsmtd); /* Unregisterd drived */
+       nand_release(nsmtd); /* Unregister driver */
+       for (i = 0;i < ARRAY_SIZE(ns->partitions); ++i)
+               kfree(ns->partitions[i].name);
        kfree(nsmtd);        /* Free other structures */
+       free_lists();
 }
 
 module_exit(ns_cleanup_module);
@@ -1509,4 +1953,3 @@ module_exit(ns_cleanup_module);
 MODULE_LICENSE ("GPL");
 MODULE_AUTHOR ("Artem B. Bityuckiy");
 MODULE_DESCRIPTION ("The NAND flash simulator");
-