From 502690674281a047abd45f81e64c498bc23a8bb3 Mon Sep 17 00:00:00 2001 From: "david.oberhollenzer@sigma-star.at" Date: Thu, 26 Mar 2015 23:59:50 +0100 Subject: UBI: power cut emulation for testing 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 Signed-off-by: Richard Weinberger diff --git a/drivers/mtd/ubi/debug.c b/drivers/mtd/ubi/debug.c index 224fed0..b077e43 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); @@ -283,6 +283,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; @@ -311,7 +327,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); @@ -325,6 +341,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') @@ -438,6 +469,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: @@ -458,3 +510,36 @@ 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.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_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 c6ea443..eb8985e 100644 --- a/drivers/mtd/ubi/debug.h +++ b/drivers/mtd/ubi/debug.h @@ -137,4 +137,6 @@ static inline void ubi_enable_dbg_chk_fastmap(struct ubi_device *ubi) { ubi->dbg.chk_fastmap = 1; } + +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 ed0bcb3..5bbd1f0 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 3d58c74..c998212 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 @@ -360,6 +371,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 @@ -368,6 +383,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; @@ -376,6 +394,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; @@ -384,6 +406,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; }; /** -- cgit v0.10.2