From: David Oberhollenzer <david.oberhollen...@sigma-star.at>

Emulate random power cuts by switching device to ro after a number of
writes to allow simple power cut testing with nand-sim.

Maximum and minimum number of successful writes before power cut and
what kind of writes (EC header, VID header or none) to interrupt
configurable via debugfs.

Signed-off-by: David Oberhollenzer <david.oberhollen...@sigma-star.at>
---
 drivers/mtd/ubi/debug.c | 93 +++++++++++++++++++++++++++++++++++++++++++++++--
 drivers/mtd/ubi/debug.h |  2 ++
 drivers/mtd/ubi/io.c    |  6 ++++
 drivers/mtd/ubi/ubi.h   | 25 +++++++++++++
 4 files changed, 124 insertions(+), 2 deletions(-)

diff --git a/drivers/mtd/ubi/debug.c b/drivers/mtd/ubi/debug.c
index 7335c9f..f2ce1b9 100644
--- a/drivers/mtd/ubi/debug.c
+++ b/drivers/mtd/ubi/debug.c
@@ -263,7 +263,7 @@ static ssize_t dfs_file_read(struct file *file, char __user 
*user_buf,
        struct dentry *dent = file->f_path.dentry;
        struct ubi_device *ubi;
        struct ubi_debug_info *d;
-       char buf[3];
+       char buf[8];
        int val;
 
        ubi = ubi_get_device(ubi_num);
@@ -281,6 +281,22 @@ static ssize_t dfs_file_read(struct file *file, char 
__user *user_buf,
                val = d->emulate_bitflips;
        else if (dent == d->dfs_emulate_io_failures)
                val = d->emulate_io_failures;
+       else if (dent == d->dfs_emulate_power_cut) {
+               snprintf(buf, sizeof(buf), "%u\n", d->emulate_power_cut);
+               count = simple_read_from_buffer(user_buf, count, ppos,
+                                               buf, strlen(buf));
+               goto out;
+       } else if (dent == d->dfs_power_cut_min) {
+               snprintf(buf, sizeof(buf), "%u\n", d->power_cut_min);
+               count = simple_read_from_buffer(user_buf, count, ppos,
+                                               buf, strlen(buf));
+               goto out;
+       } else if (dent == d->dfs_power_cut_max) {
+               snprintf(buf, sizeof(buf), "%u\n", d->power_cut_max);
+               count = simple_read_from_buffer(user_buf, count, ppos,
+                                               buf, strlen(buf));
+               goto out;
+       }
        else {
                count = -EINVAL;
                goto out;
@@ -309,7 +325,7 @@ static ssize_t dfs_file_write(struct file *file, const char 
__user *user_buf,
        struct ubi_device *ubi;
        struct ubi_debug_info *d;
        size_t buf_size;
-       char buf[8];
+       char buf[8] = {0};
        int val;
 
        ubi = ubi_get_device(ubi_num);
@@ -323,6 +339,21 @@ static ssize_t dfs_file_write(struct file *file, const 
char __user *user_buf,
                goto out;
        }
 
+       if (dent == d->dfs_power_cut_min) {
+               if (kstrtouint(buf, 0, &d->power_cut_min) != 0)
+                       count = -EINVAL;
+               goto out;
+       } else if (dent == d->dfs_power_cut_max) {
+               if (kstrtouint(buf, 0, &d->power_cut_max) != 0)
+                       count = -EINVAL;
+               goto out;
+       } else if (dent == d->dfs_emulate_power_cut) {
+               if (kstrtoint(buf, 0, &val) != 0)
+                       count = -EINVAL;
+               d->emulate_power_cut = val;
+               goto out;
+       }
+
        if (buf[0] == '1')
                val = 1;
        else if (buf[0] == '0')
@@ -427,6 +458,27 @@ int ubi_debugfs_init_dev(struct ubi_device *ubi)
                goto out_remove;
        d->dfs_emulate_io_failures = dent;
 
+       fname = "tst_emulate_power_cut";
+       dent = debugfs_create_file(fname, S_IWUSR, d->dfs_dir, (void *)ubi_num,
+                                  &dfs_fops);
+       if (IS_ERR_OR_NULL(dent))
+               goto out_remove;
+       d->dfs_emulate_power_cut = dent;
+
+       fname = "tst_emulate_power_cut_min";
+       dent = debugfs_create_file(fname, S_IWUSR, d->dfs_dir, (void *)ubi_num,
+                                  &dfs_fops);
+       if (IS_ERR_OR_NULL(dent))
+               goto out_remove;
+       d->dfs_power_cut_min = dent;
+
+       fname = "tst_emulate_power_cut_max";
+       dent = debugfs_create_file(fname, S_IWUSR, d->dfs_dir, (void *)ubi_num,
+                                  &dfs_fops);
+       if (IS_ERR_OR_NULL(dent))
+               goto out_remove;
+       d->dfs_power_cut_max = dent;
+
        return 0;
 
 out_remove:
@@ -447,3 +499,40 @@ void ubi_debugfs_exit_dev(struct ubi_device *ubi)
        if (IS_ENABLED(CONFIG_DEBUG_FS))
                debugfs_remove_recursive(ubi->dbg.dfs_dir);
 }
+
+/**
+ * ubi_dbg_power_cut - emulate a power cut if it is time to do so
+ * @ubi: UBI device description object
+ * @caller: Flags set to indicate from where the function is being called
+ *
+ * Returns non-zero if a power cut was emulated, zero if not.
+ */
+int ubi_dbg_power_cut(struct ubi_device *ubi, int caller)
+{
+       unsigned int range;
+
+       if (ubi->dbg.power_cut_counter < 0)
+               return 0;
+
+       if ((ubi->dbg.emulate_power_cut & caller) == 0)
+               return 0;
+
+       if (ubi->dbg.power_cut_counter == 0) {
+               ubi->dbg.power_cut_counter = ubi->dbg.power_cut_min;
+
+               if (ubi->dbg.power_cut_max > ubi->dbg.power_cut_min) {
+                       range = ubi->dbg.power_cut_max - ubi->dbg.power_cut_min;
+                       ubi->dbg.power_cut_counter += prandom_u32() % range;
+               }
+               return 0;
+       }
+
+       ubi->dbg.power_cut_counter--;
+       if (ubi->dbg.power_cut_counter)
+               return 0;
+
+       ubi->dbg.power_cut_counter = -1;
+       ubi_msg(ubi, "XXXXXXXXXXXXXXX emulating a power cut XXXXXXXXXXXXXXXX");
+       ubi_ro_mode(ubi);
+       return 1;
+}
diff --git a/drivers/mtd/ubi/debug.h b/drivers/mtd/ubi/debug.h
index cba89fc..d74b339 100644
--- a/drivers/mtd/ubi/debug.h
+++ b/drivers/mtd/ubi/debug.h
@@ -127,4 +127,6 @@ static inline int ubi_dbg_chk_gen(const struct ubi_device 
*ubi)
 {
        return ubi->dbg.chk_gen;
 }
+
+int ubi_dbg_power_cut(struct ubi_device *ubi, int caller);
 #endif /* !__UBI_DEBUG_H__ */
diff --git a/drivers/mtd/ubi/io.c b/drivers/mtd/ubi/io.c
index 396aaa5..f77de8e 100644
--- a/drivers/mtd/ubi/io.c
+++ b/drivers/mtd/ubi/io.c
@@ -859,6 +859,9 @@ int ubi_io_write_ec_hdr(struct ubi_device *ubi, int pnum,
        if (err)
                return err;
 
+       if (ubi_dbg_power_cut(ubi, POWER_CUT_EC_WRITE))
+               return -EROFS;
+
        err = ubi_io_write(ubi, ec_hdr, pnum, 0, ubi->ec_hdr_alsize);
        return err;
 }
@@ -1106,6 +1109,9 @@ int ubi_io_write_vid_hdr(struct ubi_device *ubi, int pnum,
        if (err)
                return err;
 
+       if (ubi_dbg_power_cut(ubi, POWER_CUT_VID_WRITE))
+               return -EROFS;
+
        p = (char *)vid_hdr - ubi->vid_hdr_shift;
        err = ubi_io_write(ubi, p, pnum, ubi->vid_hdr_aloffset,
                           ubi->vid_hdr_alsize);
diff --git a/drivers/mtd/ubi/ubi.h b/drivers/mtd/ubi/ubi.h
index f80ffab..3886fdc 100644
--- a/drivers/mtd/ubi/ubi.h
+++ b/drivers/mtd/ubi/ubi.h
@@ -151,6 +151,17 @@ enum {
        UBI_BAD_FASTMAP,
 };
 
+/*
+ * Flags for emulate_power_cut in ubi_debug_info
+ *
+ * POWER_CUT_EC_WRITE: Emulate a power cut when writing an EC header
+ * POWER_CUT_VID_WRITE: Emulate a power cut when writing a VID header
+ */
+enum {
+       POWER_CUT_EC_WRITE = 0x01,
+       POWER_CUT_VID_WRITE = 0x02,
+};
+
 /**
  * struct ubi_wl_entry - wear-leveling entry.
  * @u.rb: link in the corresponding (free/used) RB-tree
@@ -356,6 +367,10 @@ struct ubi_wl_entry;
  * @disable_bgt: disable the background task for testing purposes
  * @emulate_bitflips: emulate bit-flips for testing purposes
  * @emulate_io_failures: emulate write/erase failures for testing purposes
+ * @emulate_power_cut: emulate power cut for testing purposes
+ * @power_cut_counter: count down for writes left until emulated power cut
+ * @power_cut_min: minimum number of writes before emulating a power cut
+ * @power_cut_max: maximum number of writes until emulating a power cut
  * @dfs_dir_name: name of debugfs directory containing files of this UBI device
  * @dfs_dir: direntry object of the UBI device debugfs directory
  * @dfs_chk_gen: debugfs knob to enable UBI general extra checks
@@ -363,6 +378,9 @@ struct ubi_wl_entry;
  * @dfs_disable_bgt: debugfs knob to disable the background task
  * @dfs_emulate_bitflips: debugfs knob to emulate bit-flips
  * @dfs_emulate_io_failures: debugfs knob to emulate write/erase failures
+ * @dfs_emulate_power_cut: debugfs knob to emulate power cuts
+ * @dfs_power_cut_min: debugfs knob for minimum writes before power cut
+ * @dfs_power_cut_max: debugfs knob for maximum writes until power cut
  */
 struct ubi_debug_info {
        unsigned int chk_gen:1;
@@ -370,6 +388,10 @@ struct ubi_debug_info {
        unsigned int disable_bgt:1;
        unsigned int emulate_bitflips:1;
        unsigned int emulate_io_failures:1;
+       unsigned int emulate_power_cut:2;
+       unsigned int power_cut_counter;
+       unsigned int power_cut_min;
+       unsigned int power_cut_max;
        char dfs_dir_name[UBI_DFS_DIR_LEN + 1];
        struct dentry *dfs_dir;
        struct dentry *dfs_chk_gen;
@@ -377,6 +399,9 @@ struct ubi_debug_info {
        struct dentry *dfs_disable_bgt;
        struct dentry *dfs_emulate_bitflips;
        struct dentry *dfs_emulate_io_failures;
+       struct dentry *dfs_emulate_power_cut;
+       struct dentry *dfs_power_cut_min;
+       struct dentry *dfs_power_cut_max;
 };
 
 /**
-- 
2.2.2

--
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