[PATCH 1/1] md dm-dust: Functionality for write error emulation along with the existing read error functionality

From: sekhar satapathy soumendu
Date: Sun Dec 01 2019 - 16:35:36 EST


Hi there,

I have modified the existing dm-dust functionality(all changes have
been applied to one file drivers/md/dust.c ) to add write error
emulation along with the existing read error. Intention was not to
change the existing functionality but to have a add-on write error
emulation along with it.I thought this may be a good to have
modification for the testers who would like to have a functionality to
add/remove bad blocks at will for "write error" and be able to
modulate it along with the existing "read error" emulation
functionality that dm-dust already has.

Thank you all for you time. The following is the patch.


Signed-off-by: Soumendu Sekhar Satapathy <satapathy.soumendu@xxxxxxxxx>



--- linux-5.4.1.old/drivers/md/dm-dust.c 2019-11-29 04:10:32.000000000 -0500
+++ linux-5.4.1.new/drivers/md/dm-dust.c 2019-12-01 15:19:32.784275979 -0500
@@ -1,3 +1,4 @@
+
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2018 Red Hat, Inc.
@@ -6,6 +7,22 @@
* sectors, emulating the behavior of a hard disk drive sending
* a "Read Medium Error" sense.
*
+ *
+ *
+ *
+ * Soumendu Sekhar Satapathy email satapathy.soumendu@xxxxxxxxx
+ * 30th Nov 2019
+ * I have modified the ~/drivers/md/dm-dust.c file present in the
+ * linux kernel stable ver 5.4.1 (version as of writing this comment)
+ * and have added functionality for emulating disk with "write errors"
+ * also. The added functionality does not intend to change the existing
+ * read error functionality. The added functionality which is added is
+ * in same lines as the already existing read error functionality.
+ *
+ * Soumendu Sekhar Satapathy email satapathy.soumendu@xxxxxxxxx
+ * 01 Dec 2019
+ * Did some code cleanups
+ *
*/

#include <linux/device-mapper.h>
@@ -14,20 +31,27 @@

#define DM_MSG_PREFIX "dust"

+#define RD false
+#define WR true
+
struct badblock {
struct rb_node node;
sector_t bb;
+ unsigned char wr_fail_cnt;
};

struct dust_device {
struct dm_dev *dev;
- struct rb_root badblocklist;
- unsigned long long badblock_count;
+ struct rb_root badblocklist_read;
+ struct rb_root badblocklist_write;
+ unsigned long long badblock_count_read;
+ unsigned long long badblock_count_write;
spinlock_t dust_lock;
unsigned int blksz;
int sect_per_block_shift;
unsigned int sect_per_block;
sector_t start;
+ bool fail_write_on_bb:1;
bool fail_read_on_bb:1;
bool quiet_mode:1;
};
@@ -50,6 +74,7 @@
return NULL;
}

+
static bool dust_rb_insert(struct rb_root *root, struct badblock *new)
{
struct badblock *bblk;
@@ -74,25 +99,34 @@
return true;
}

-static int dust_remove_block(struct dust_device *dd, unsigned long long block)
+static int dust_remove_block(struct dust_device *dd, unsigned long
long block, bool mode)
{
struct badblock *bblock;
unsigned long flags;

spin_lock_irqsave(&dd->dust_lock, flags);
- bblock = dust_rb_search(&dd->badblocklist, block);
+ if(mode == RD)
+ bblock = dust_rb_search(&dd->badblocklist_read, block);
+ else
+ bblock = dust_rb_search(&dd->badblocklist_write, block);

if (bblock == NULL) {
if (!dd->quiet_mode) {
- DMERR("%s: block %llu not found in badblocklist",
+ DMERR("%s: block %llu not found in badblocklist_read",
__func__, block);
}
spin_unlock_irqrestore(&dd->dust_lock, flags);
return -EINVAL;
}

- rb_erase(&bblock->node, &dd->badblocklist);
- dd->badblock_count--;
+ if(mode == RD)
+ rb_erase(&bblock->node, &dd->badblocklist_read);
+ else
+ rb_erase(&bblock->node, &dd->badblocklist_write);
+ if(mode == RD)
+ dd->badblock_count_read--;
+ else
+ dd->badblock_count_write--;
if (!dd->quiet_mode)
DMINFO("%s: badblock removed at block %llu", __func__, block);
kfree(bblock);
@@ -101,7 +135,8 @@
return 0;
}

-static int dust_add_block(struct dust_device *dd, unsigned long long block)
+static int dust_add_block(struct dust_device *dd, unsigned long long block,
+ unsigned char wr_fail_cnt, bool mode)
{
struct badblock *bblock;
unsigned long flags;
@@ -115,31 +150,55 @@

spin_lock_irqsave(&dd->dust_lock, flags);
bblock->bb = block;
- if (!dust_rb_insert(&dd->badblocklist, bblock)) {
- if (!dd->quiet_mode) {
- DMERR("%s: block %llu already in badblocklist",
- __func__, block);
+ bblock->wr_fail_cnt = wr_fail_cnt;
+ if(mode == RD) {
+ if (!dust_rb_insert(&dd->badblocklist_read, bblock)) {
+ if (!dd->quiet_mode) {
+ DMERR("%s: block %llu already in badblocklist",
+ __func__, block);
+ }
+ spin_unlock_irqrestore(&dd->dust_lock, flags);
+ kfree(bblock);
+ return -EINVAL;
+ }
+ }
+ else {
+ if (!dust_rb_insert(&dd->badblocklist_write, bblock)) {
+ if (!dd->quiet_mode) {
+ DMERR("%s: block %llu already in badblocklist",
+ __func__, block);
+ }
+ spin_unlock_irqrestore(&dd->dust_lock, flags);
+ kfree(bblock);
+ return -EINVAL;
}
- spin_unlock_irqrestore(&dd->dust_lock, flags);
- kfree(bblock);
- return -EINVAL;
}

- dd->badblock_count++;
- if (!dd->quiet_mode)
- DMINFO("%s: badblock added at block %llu", __func__, block);
+ if(mode == RD)
+ dd->badblock_count_read++;
+ else
+ dd->badblock_count_write++;
+
+ if (!dd->quiet_mode) {
+ DMINFO("%s: badblock added at block %llu with write fail count %hhu",
+ __func__, block, wr_fail_cnt);
+ }
spin_unlock_irqrestore(&dd->dust_lock, flags);

return 0;
}

-static int dust_query_block(struct dust_device *dd, unsigned long long block)
+
+static int dust_query_block(struct dust_device *dd, unsigned long
long block, bool mode)
{
struct badblock *bblock;
unsigned long flags;

spin_lock_irqsave(&dd->dust_lock, flags);
- bblock = dust_rb_search(&dd->badblocklist, block);
+ if(mode == RD)
+ bblock = dust_rb_search(&dd->badblocklist_read, block);
+ else
+ bblock = dust_rb_search(&dd->badblocklist_write, block);
if (bblock != NULL)
DMINFO("%s: block %llu found in badblocklist", __func__, block);
else
@@ -151,7 +210,7 @@

static int __dust_map_read(struct dust_device *dd, sector_t thisblock)
{
- struct badblock *bblk = dust_rb_search(&dd->badblocklist, thisblock);
+ struct badblock *bblk = dust_rb_search(&dd->badblocklist_read, thisblock);

if (bblk)
return DM_MAPIO_KILL;
@@ -163,63 +222,85 @@
bool fail_read_on_bb)
{
unsigned long flags;
- int ret = DM_MAPIO_REMAPPED;
+ int r = DM_MAPIO_REMAPPED;

if (fail_read_on_bb) {
thisblock >>= dd->sect_per_block_shift;
spin_lock_irqsave(&dd->dust_lock, flags);
- ret = __dust_map_read(dd, thisblock);
+ r = __dust_map_read(dd, thisblock);
spin_unlock_irqrestore(&dd->dust_lock, flags);
}

- return ret;
+ return r;
}

-static void __dust_map_write(struct dust_device *dd, sector_t thisblock)
+static int __dust_map_write(struct dust_device *dd, sector_t thisblock)
{
- struct badblock *bblk = dust_rb_search(&dd->badblocklist, thisblock);
+ struct badblock *bblk_r = dust_rb_search(&dd->badblocklist_read,
thisblock);
+ struct badblock *bblk_w = dust_rb_search(&dd->badblocklist_write,
thisblock);

- if (bblk) {
- rb_erase(&bblk->node, &dd->badblocklist);
- dd->badblock_count--;
- kfree(bblk);
- if (!dd->quiet_mode) {
- sector_div(thisblock, dd->sect_per_block);
- DMINFO("block %llu removed from badblocklist by write",
- (unsigned long long)thisblock);
+ if(dd->fail_write_on_bb) {
+ if (bblk_w)
+ return DM_MAPIO_KILL;
+ }
+
+ if(dd->fail_read_on_bb) {
+ if (bblk_r && bblk_r->wr_fail_cnt > 0) {
+ bblk_r->wr_fail_cnt--;
+ return DM_MAPIO_KILL;
+ }
+
+ if (bblk_r) {
+ rb_erase(&bblk_r->node, &dd->badblocklist_read);
+ dd->badblock_count_read--;
+ kfree(bblk_r);
+ if (!dd->quiet_mode) {
+ sector_div(thisblock, dd->sect_per_block);
+ DMINFO("block %llu removed from badblocklist_read by write",
+ (unsigned long long)thisblock);
+ }
}
}
+
+ return DM_MAPIO_REMAPPED;
}

static int dust_map_write(struct dust_device *dd, sector_t thisblock,
- bool fail_read_on_bb)
+ bool fail_read_on_bb, bool fail_write_on_bb)
{
unsigned long flags;
+ int ret = DM_MAPIO_REMAPPED;

- if (fail_read_on_bb) {
+ if (fail_write_on_bb) {
thisblock >>= dd->sect_per_block_shift;
spin_lock_irqsave(&dd->dust_lock, flags);
- __dust_map_write(dd, thisblock);
+ ret = __dust_map_write(dd, thisblock);
+ spin_unlock_irqrestore(&dd->dust_lock, flags);
+ }
+ else if (fail_read_on_bb) {
+ thisblock >>= dd->sect_per_block_shift;
+ spin_lock_irqsave(&dd->dust_lock, flags);
+ ret = __dust_map_write(dd, thisblock);
spin_unlock_irqrestore(&dd->dust_lock, flags);
}

- return DM_MAPIO_REMAPPED;
+ return ret;
}

static int dust_map(struct dm_target *ti, struct bio *bio)
{
struct dust_device *dd = ti->private;
- int ret;
+ int r;

bio_set_dev(bio, dd->dev->bdev);
bio->bi_iter.bi_sector = dd->start + dm_target_offset(ti,
bio->bi_iter.bi_sector);

if (bio_data_dir(bio) == READ)
- ret = dust_map_read(dd, bio->bi_iter.bi_sector, dd->fail_read_on_bb);
+ r = dust_map_read(dd, bio->bi_iter.bi_sector, dd->fail_read_on_bb);
else
- ret = dust_map_write(dd, bio->bi_iter.bi_sector, dd->fail_read_on_bb);
+ r = dust_map_write(dd, bio->bi_iter.bi_sector,
dd->fail_read_on_bb, dd->fail_write_on_bb);

- return ret;
+ return r;
}

static bool __dust_clear_badblocks(struct rb_root *tree,
@@ -246,23 +327,41 @@
return true;
}

-static int dust_clear_badblocks(struct dust_device *dd)
+static int dust_clear_badblocks(struct dust_device *dd, bool mode)
{
unsigned long flags;
- struct rb_root badblocklist;
- unsigned long long badblock_count;
+ struct rb_root badblocklist_read;
+ struct rb_root badblocklist_write;
+ unsigned long long badblock_count_read;
+ unsigned long long badblock_count_write;

spin_lock_irqsave(&dd->dust_lock, flags);
- badblocklist = dd->badblocklist;
- badblock_count = dd->badblock_count;
- dd->badblocklist = RB_ROOT;
- dd->badblock_count = 0;
+ if(mode == RD) {
+ badblocklist_read = dd->badblocklist_read;
+ badblock_count_read = dd->badblock_count_read;
+ dd->badblocklist_read = RB_ROOT;
+ dd->badblock_count_read = 0;
+ }
+ else {
+ badblocklist_write = dd->badblocklist_write;
+ badblock_count_write = dd->badblock_count_write;
+ dd->badblocklist_write = RB_ROOT;
+ dd->badblock_count_write = 0;
+ }
spin_unlock_irqrestore(&dd->dust_lock, flags);

- if (!__dust_clear_badblocks(&badblocklist, badblock_count))
- DMINFO("%s: no badblocks found", __func__);
- else
- DMINFO("%s: badblocks cleared", __func__);
+ if(mode == RD) {
+ if (!__dust_clear_badblocks(&badblocklist_read, badblock_count_read))
+ DMINFO("%s: no read badblocks found", __func__);
+ else
+ DMINFO("%s: read badblocks cleared", __func__);
+ }
+ else {
+ if (!__dust_clear_badblocks(&badblocklist_write, badblock_count_write))
+ DMINFO("%s: no write badblocks found", __func__);
+ else
+ DMINFO("%s: write badblocks cleared", __func__);
+ }

return 0;
}
@@ -343,10 +442,18 @@
dd->fail_read_on_bb = false;

/*
+ * Fail a write on a "bad" block.
+ * Defaults to false; enabled later by message.
+ */
+ dd->fail_write_on_bb = false;
+
+ /*
* Initialize bad block list rbtree.
*/
- dd->badblocklist = RB_ROOT;
- dd->badblock_count = 0;
+ dd->badblocklist_read = RB_ROOT;
+ dd->badblock_count_read = 0;
+ dd->badblocklist_write = RB_ROOT;
+ dd->badblock_count_write = 0;
spin_lock_init(&dd->dust_lock);

dd->quiet_mode = false;
@@ -364,7 +471,8 @@
{
struct dust_device *dd = ti->private;

- __dust_clear_badblocks(&dd->badblocklist, dd->badblock_count);
+ __dust_clear_badblocks(&dd->badblocklist_read, dd->badblock_count_read);
+ __dust_clear_badblocks(&dd->badblocklist_write, dd->badblock_count_write);
dm_put_device(ti, dd->dev);
kfree(dd);
}
@@ -375,8 +483,10 @@
struct dust_device *dd = ti->private;
sector_t size = i_size_read(dd->dev->bdev->bd_inode) >> SECTOR_SHIFT;
bool invalid_msg = false;
- int result = -EINVAL;
+ int r = -EINVAL;
unsigned long long tmp, block;
+ unsigned char wr_fail_cnt;
+ unsigned int tmp_ui;
unsigned long flags;
char dummy;

@@ -384,59 +494,204 @@
if (!strcasecmp(argv[0], "addbadblock") ||
!strcasecmp(argv[0], "removebadblock") ||
!strcasecmp(argv[0], "queryblock")) {
- DMERR("%s requires an additional argument", argv[0]);
+ DMERR("%s requires 2 additional argument", argv[0]);
} else if (!strcasecmp(argv[0], "disable")) {
- DMINFO("disabling read failures on bad sectors");
- dd->fail_read_on_bb = false;
- result = 0;
+ DMERR("%s requires 1 additional argument", argv[0]);
} else if (!strcasecmp(argv[0], "enable")) {
- DMINFO("enabling read failures on bad sectors");
- dd->fail_read_on_bb = true;
- result = 0;
+ DMERR("%s requires 1 additional argument", argv[0]);
} else if (!strcasecmp(argv[0], "countbadblocks")) {
- spin_lock_irqsave(&dd->dust_lock, flags);
- DMINFO("countbadblocks: %llu badblock(s) found",
- dd->badblock_count);
- spin_unlock_irqrestore(&dd->dust_lock, flags);
- result = 0;
+ DMERR("%s requires 1 additional argument", argv[0]);
} else if (!strcasecmp(argv[0], "clearbadblocks")) {
- result = dust_clear_badblocks(dd);
+ DMERR("%s requires 1 additional argument", argv[0]);
} else if (!strcasecmp(argv[0], "quiet")) {
if (!dd->quiet_mode)
dd->quiet_mode = true;
else
dd->quiet_mode = false;
- result = 0;
+ r = 0;
} else {
invalid_msg = true;
}
} else if (argc == 2) {
- if (sscanf(argv[1], "%llu%c", &tmp, &dummy) != 1)
- return result;
+ if (!strcasecmp(argv[0], "addbadblock")) {
+ if (!strcasecmp(argv[1], "read"))
+ DMERR("%s requires 1 additional argument", argv[0]);
+ else if (!strcasecmp(argv[1], "write"))
+ DMERR("%s requires 1 additional argument", argv[0]);
+ else
+ invalid_msg = true;
+ }
+ else if (!strcasecmp(argv[0], "enable")) {
+ if (!strcasecmp(argv[1], "read")) {
+ DMINFO("enabling read failures on bad sectors");
+ dd->fail_read_on_bb = true;
+ r = 0;
+ invalid_msg = false;
+ }
+ else if (!strcasecmp(argv[1], "write")) {
+ DMINFO("enabling write failures on bad sectors");
+ dd->fail_write_on_bb = true;
+ r = 0;
+ invalid_msg = false;
+ }
+ else
+ invalid_msg = true;
+ }
+ else if (!strcasecmp(argv[0], "disable")) {
+ if (!strcasecmp(argv[1], "read")) {
+ DMINFO("disabling read failures on bad sectors");
+ dd->fail_read_on_bb = false;
+ r = 0;
+ invalid_msg = false;
+ }
+ else if (!strcasecmp(argv[1], "write")) {
+ DMINFO("disabling write failures on bad sectors");
+ dd->fail_write_on_bb = false;
+ r = 0;
+ invalid_msg = false;
+ }
+ else
+ invalid_msg = true;
+ }
+ else if (!strcasecmp(argv[0], "removebadblock")) {
+ if (!strcasecmp(argv[1], "read"))
+ DMERR("%s requires 1 additional argument", argv[0]);
+ else if (!strcasecmp(argv[1], "write"))
+ DMERR("%s requires 1 additional argument", argv[0]);
+ else
+ invalid_msg = true;
+ }
+ else if (!strcasecmp(argv[0], "queryblock")) {
+ if (!strcasecmp(argv[1], "read"))
+ DMERR("%s requires 1 additional argument", argv[0]);
+ else if (!strcasecmp(argv[1], "write"))
+ DMERR("%s requires 1 additional argument", argv[0]);
+ else
+ invalid_msg = true;
+ }
+ else if (!strcasecmp(argv[0], "clearbadblocks")) {
+ if (!strcasecmp(argv[1], "read")) {
+ r = dust_clear_badblocks(dd,false);
+ invalid_msg = false;
+ }
+ else if (!strcasecmp(argv[1], "write")) {
+ r = dust_clear_badblocks(dd,true);
+ invalid_msg = false;
+ }
+ else
+ invalid_msg = true;
+ }
+ else if (!strcasecmp(argv[0], "countbadblocks")) {
+ if (!strcasecmp(argv[1], "read")) {
+ spin_lock_irqsave(&dd->dust_lock, flags);
+ DMINFO("countbadblocks: %llu read badblock(s) found",
+ dd->badblock_count_read);
+ spin_unlock_irqrestore(&dd->dust_lock, flags);
+ r = 0;
+ invalid_msg = false;
+ }
+ else if (!strcasecmp(argv[1], "write")) {
+ spin_lock_irqsave(&dd->dust_lock, flags);
+ DMINFO("countbadblocks: %llu write badblock(s) found",
+ dd->badblock_count_write);
+ spin_unlock_irqrestore(&dd->dust_lock, flags);
+ r = 0;
+ invalid_msg = false;
+ }
+ else
+ invalid_msg = true;
+ }
+ else
+ invalid_msg = true;
+ } else if (argc == 3) {
+ if (sscanf(argv[2], "%llu%c", &tmp, &dummy) != 1)
+ return r;

block = tmp;
sector_div(size, dd->sect_per_block);
if (block > size) {
DMERR("selected block value out of range");
- return result;
+ return r;
}

- if (!strcasecmp(argv[0], "addbadblock"))
- result = dust_add_block(dd, block);
- else if (!strcasecmp(argv[0], "removebadblock"))
- result = dust_remove_block(dd, block);
- else if (!strcasecmp(argv[0], "queryblock"))
- result = dust_query_block(dd, block);
+ if (!strcasecmp(argv[0], "addbadblock")) {
+ if (!strcasecmp(argv[1], "read")) {
+ r = dust_add_block(dd, block, 0, false);
+ invalid_msg = false;
+ }
+ else if (!strcasecmp(argv[1], "write")) {
+ r = dust_add_block(dd, block, 0, true);
+ invalid_msg = false;
+ }
+ else
+ invalid_msg = true;
+ }
+ else if (!strcasecmp(argv[0], "removebadblock")) {
+ if (!strcasecmp(argv[1], "read")) {
+ r = dust_remove_block(dd, block, false);
+ invalid_msg = false;
+ }
+ else if (!strcasecmp(argv[1], "write")) {
+ r = dust_remove_block(dd, block, true);
+ invalid_msg = false;
+ }
+ else
+ invalid_msg = true;
+ }
+ else if (!strcasecmp(argv[0], "queryblock")) {
+ if (!strcasecmp(argv[1], "read")) {
+ r = dust_query_block(dd, block, false);
+ invalid_msg = false;
+ }
+ else if (!strcasecmp(argv[1], "write")) {
+ r = dust_query_block(dd, block, true);
+ invalid_msg = false;
+ }
+ else
+ invalid_msg = true;
+ }
else
invalid_msg = true;
-
+ } else if (argc == 4) {
+ if (sscanf(argv[2], "%llu%c", &tmp, &dummy) != 1)
+ return r;
+
+ if (sscanf(argv[3], "%u%c", &tmp_ui, &dummy) != 1)
+ return r;
+
+ block = tmp;
+ if (tmp_ui > 255) {
+ DMERR("selected write fail count out of range");
+ return r;
+ }
+ wr_fail_cnt = tmp_ui;
+ sector_div(size, dd->sect_per_block);
+ if (block > size) {
+ DMERR("selected block value out of range");
+ return r;
+ }
+
+ if (!strcasecmp(argv[0], "addbadblock")) {
+ if (!strcasecmp(argv[1], "read")) {
+ r = dust_add_block(dd, block, wr_fail_cnt, false);
+ invalid_msg = false;
+ }
+ else if (!strcasecmp(argv[1], "write")) {
+ r = dust_add_block(dd, block, wr_fail_cnt, true);
+ invalid_msg = false;
+ }
+ else
+ invalid_msg = true;
+ }
+ else
+ invalid_msg = true;
} else
DMERR("invalid number of arguments '%d'", argc);

if (invalid_msg)
DMERR("unrecognized message '%s' received", argv[0]);

- return result;
+ return r;
}

static void dust_status(struct dm_target *ti, status_type_t type,
@@ -450,6 +705,9 @@
DMEMIT("%s %s %s", dd->dev->name,
dd->fail_read_on_bb ? "fail_read_on_bad_block" : "bypass",
dd->quiet_mode ? "quiet" : "verbose");
+ DMEMIT("\n%s %s %s", dd->dev->name,
+ dd->fail_write_on_bb ? "fail_write_on_bad_block" : "bypass",
+ dd->quiet_mode ? "quiet" : "verbose");
break;

case STATUSTYPE_TABLE:
@@ -499,12 +757,12 @@

static int __init dm_dust_init(void)
{
- int result = dm_register_target(&dust_target);
+ int r = dm_register_target(&dust_target);

- if (result < 0)
- DMERR("dm_register_target failed %d", result);
+ if (r < 0)
+ DMERR("dm_register_target failed %d", r);

- return result;
+ return r;
}

static void __exit dm_dust_exit(void)