On Mon, Jan 07, 2019 at 02:31:23PM -0500, Bryan Gurney wrote:
> Add the dm-dust target, which simulates the behavior of bad sectors
> at arbitrary locations, and the ability to enable the emulation of
> the read failures at an arbitrary time.
> 
> This target behaves similarly to a linear target, with the exception
> of the minimum and optimal IO sizes being configurable to 512 or
> 4096 bytes.
> 
> At a given time, the user can send a message to the target to start
> failing read requests on specific blocks.  When the failure behavior
> is enabled, reads of blocks configured "bad" will fail with EIO.
> 
> Writes of blocks configured "bad" will result in the following:
> 
> 1. Remove the block from the "bad block list".
> 2. Successfully complete the write.
> 
> After this point, the block will successfully contain the written
> data, and will service reads and writes normally.  This emulates the
> behavior of a "remapped sector" on a hard disk drive.
> 
> Signed-off-by: Joe Shimkus <jshim...@redhat.com>
> Signed-off-by: John Dorminy <jdorm...@redhat.com>
> Signed-off-by: John Pittman <jpitt...@redhat.com>
> Signed-off-by: Thomas Jaskiewicz <tjask...@redhat.com>
> Signed-off-by: Bryan Gurney <bgur...@redhat.com>
> ---
>  Documentation/device-mapper/dm-dust.txt | 256 ++++++++++++
>  drivers/md/Kconfig                      |   9 +
>  drivers/md/Makefile                     |   1 +
>  drivers/md/dm-dust.c                    | 499 ++++++++++++++++++++++++
>  4 files changed, 765 insertions(+)
>  create mode 100644 Documentation/device-mapper/dm-dust.txt
>  create mode 100644 drivers/md/dm-dust.c
> 
> diff --git a/Documentation/device-mapper/dm-dust.txt 
> b/Documentation/device-mapper/dm-dust.txt
> new file mode 100644
> index 000000000000..7d7740d5b1c9
> --- /dev/null
> +++ b/Documentation/device-mapper/dm-dust.txt
> @@ -0,0 +1,256 @@
> +dm-dust
> +=======
> +
> +This target emulates the behavior of bad sectors at arbitrary
> +locations, and the ability to enable the emulation of the failures
> +at an arbitrary time.
> +
> +This target behaves similarly to a linear target, except that the
> +minimum and optimal io sizes are DUST_BLOCK_SIZE bytes (512 or
> +4096 bytes).  At a given time, the user can send a message to the
> +target to start failing read requests on specific blocks (to emulate
> +the behavior of a hard disk drive with bad sectors).
> +
> +When the failure behavior is enabled (i.e.: when the output of
> +"dmsetup status" displays "fail_read_on_bad_block"), reads of blocks
> +in the "bad block list" will fail with EIO ("Input/output error").
> +
> +Writes of blocks in the "bad block list will result in the following:
> +
> +1. Remove the block from the "bad block list".
> +2. Successfully complete the write.
> +
> +This emulates the "remapped sector" behavior of a drive with bad
> +sectors.
> +
> +Normally, a drive that is encountering bad sectors will most likely
> +encounter more bad sectors, at an unknown time or location.
> +With dm-dust, the user can use the "addbadblock" and "removebadblock"
> +messages to add arbitrary bad blocks at new locations, and the
> +"enable" and "disable" messages to modulate the state of whether the
> +configured "bad blocks" will be treated as bad, or bypassed.
> +This allows the pre-writing of test data and metadata prior to
> +simulating a "failure" event where bad sectors start to appear.
> +
> +Table parameters:
> +-----------------
> +<device_path> <blksz>
> +
> +Mandatory parameters:
> +    <device_path>: path to the block device.
> +    <blksz>: block size (valid choices: 512, 4096).
> +
> +Usage instructions:
> +-------------------
> +
> +First, find the size (in 512-byte sectors) of the device to be used:
> +
> +$ sudo blockdev --getsz /dev/vdb1
> +33552384
> +
> +After building the module, load the module:
> +
> +$ sudo modprobe pbitdust
> +
> +Create the device:
> +(For a device with a block size of 512 bytes)
> +$ sudo dmsetup create dust1 --table '0 33552384 dust /dev/vdb1 512'
> +
> +(For a device with a block size of 4096 bytes)
> +$ sudo dmsetup create dust1 --table '0 33552384 dust /dev/vdb1 4096'
> +
> +Check the status of the read behavior ("bypass" indicates that all I/O
> +will be passed through to the underlying device):
> +$ sudo dmsetup status dust1
> +0 33552384 dust 252:17 bypass
> +
> +$ sudo dd if=/dev/mapper/dust1 of=/dev/null bs=512 count=128 iflag=direct
> +128+0 records in
> +128+0 records out
> +
> +$ sudo dd if=/dev/zero of=/dev/mapper/dust1 bs=512 count=128 oflag=direct
> +128+0 records in
> +128+0 records out
> +
> +Adding and removing bad blocks:
> +-------------------------------
> +
> +At any time (i.e.: whether the device has the "bad block" emulation
> +enabled or disabled), bad blocks may be added or removed from the
> +device via the "addbadblock" and "removebadblock" messages:
> +
> +$ sudo dmsetup message dust1 0 addbadblock 60
> +kernel: device-mapper: dust: badblock added at block 60
> +
> +$ sudo dmsetup message dust1 0 addbadblock 67
> +kernel: device-mapper: dust: badblock added at block 67
> +
> +$ sudo dmsetup message dust1 0 addbadblock 72
> +kernel: device-mapper: dust: badblock added at block 72
> +
> +These bad blocks will be stored in the "bad block list".
> +While the device is in "bypass" mode, reads and writes will succeed:
> +
> +$ sudo dmsetup status dust1
> +0 33552384 dust 252:17 bypass
> +
> +Enabling block read failures:
> +-----------------------------
> +
> +To enable the "fail read on bad block" behavior, send the "enable" message:
> +
> +$ sudo dmsetup message dust1 0 enable
> +kernel: device-mapper: dust: enabling read failures on bad sectors
> +
> +$ sudo dmsetup status dust1
> +0 33552384 dust 252:17 fail_read_on_bad_block
> +
> +With the device in "fail read on bad block" mode, attempting to read a
> +block will encounter an "Input/output error":
> +
> +$ sudo dd if=/dev/mapper/dust1 of=/dev/null bs=512 count=1 skip=67 
> iflag=direct
> +dd: error reading '/dev/mapper/dust1': Input/output error
> +0+0 records in
> +0+0 records out
> +0 bytes copied, 0.00040651 s, 0.0 kB/s
> +
> +...and writing to the bad blocks will remove the blocks from the list,
> +therefore emulating the "remap" behavior of hard disk drives:
> +
> +$ sudo dd if=/dev/zero of=/dev/mapper/dust1 bs=512 count=128 oflag=direct
> +128+0 records in
> +128+0 records out
> +
> +kernel: device-mapper: dust: block 60 removed from badblocklist by write
> +kernel: device-mapper: dust: block 67 removed from badblocklist by write
> +kernel: device-mapper: dust: block 72 removed from badblocklist by write
> +kernel: device-mapper: dust: block 87 removed from badblocklist by write
> +
> +Bad block add/remove error handling:
> +------------------------------------
> +
> +Attempting to add a bad block that already exists in the list will
> +result in an "Invalid argument" error, as well as a helpful message:
> +
> +$ sudo dmsetup message dust1 0 addbadblock 88
> +device-mapper: message ioctl on dust1  failed: Invalid argument
> +kernel: device-mapper: dust: block 88 already in badblocklist
> +
> +Attempting to remove a bad block that doesn't exist in the list will
> +result in an "Invalid argument" error, as well as a helpful message:
> +
> +$ sudo dmsetup message dust1 0 removebadblock 87
> +device-mapper: message ioctl on dust1  failed: Invalid argument
> +kernel: device-mapper: dust: block 87 not found in badblocklist
> +
> +Counting the number of bad blocks in the bad block list:
> +--------------------------------------------------------
> +
> +To count the number of bad blocks configured in the device, run the
> +following message command:
> +
> +$ sudo dmsetup message dust1 0 countbadblocks
> +
> +A message will print with the number of bad blocks currently
> +configured on the device:
> +
> +kernel: device-mapper: dust: countbadblocks: 895 badblock(s) found
> +
> +Clearing the bad block list:
> +----------------------------
> +
> +To clear the bad block list (without needing to individually run
> +a "removebadblock" message command for every block), run the
> +following message command:
> +
> +$ sudo dmsetup message dust1 0 clearbadblocks
> +
> +After clearing the bad block list, the following message will appear:
> +
> +kernel: device-mapper: dust: clearbadblocks: badblocks cleared
> +
> +If there were no bad blocks to clear, the following message will
> +appear:
> +
> +kernel: device-mapper: dust: clearbadblocks: no badblocks found
> +
> +Message commands list:
> +----------------------
> +
> +Below is a list of the messages that can be sent to a dust device:
> +
> +Operations on blocks (requires a <blknum> argument):
> +
> +addbadblock <blknum>
> +queryblock <blknum>
> +removebadblock <blknum>
> +
> +...where <blknum> is a block number within range of the device
> +  (corresponding to the block size of the device, 512 or 4096 bytes.)
> +
> +Single argument message commands:
> +
> +countbadblocks
> +clearbadblocks
> +disable
> +enable
> +quiet
> +
> +Device removal:
> +---------------
> +
> +When finished, remove the device via the "dmsetup remove" command:
> +
> +$ sudo dmsetup remove dust1
> +
> +Quiet mode:
> +-----------
> +
> +On test runs with many bad blocks, it may be desirable to avoid
> +excessive logging (from bad blocks added, removed, or "remapped").
> +This can be done by enabling "quiet mode" via the following message:
> +
> +$ sudo dmsetup message dust1 0 quiet
> +
> +This will suppress log messages from add / remove / removed by write
> +operations.  Log messages from "countbadblocks" or "queryblock"
> +message commands will still print in quiet mode.
> +
> +The status of quiet mode can be seen by running "dmsetup status":
> +
> +$ sudo dmsetup status dust1
> +0 33552384 dust 252:17 fail_read_on_bad_block quiet
> +
> +To disable quiet mode, send the "quiet" message again:
> +
> +$ sudo dmsetup message dust1 0 quiet
> +
> +$ sudo dmsetup status dust1
> +0 33552384 dust 252:17 fail_read_on_bad_block verbose
> +
> +(The presence of "verbose" indicates normal logging.)
> +
> +"Why not...?"
> +-------------
> +
> +scsi_debug has a "medium error" mode that can fail reads on one
> +specified sector (sector 0x1234, hardcoded in the source code), but
> +it uses RAM for the persistent storage, which drastically decreases
> +the potential device size.
> +
> +dm-flakey fails all I/O from all block locations at a specified time
> +frequency, and not a given point in time.
> +
> +When a bad sector occurs on a hard disk drive, reads to that sector
> +are failed by the device, usually resulting in an error code of EIO
> +("I/O error") or ENODATA ("No data available").  However, a write to
> +the sector may succeed, and result in the sector becoming readable
> +after the device controller no longer experiences errors reading the
> +sector (or after a reallocation of the sector).  However, there may
> +be bad sectors that occur on the device in the future, in a different,
> +unpredictable location.
> +
> +This target seeks to provide a device that can exhibit the behavior
> +of a bad sector at a known sector location, at a known time, based
> +on a large storage device (at least tens of gigabytes, not occupying
> +system memory).
> diff --git a/drivers/md/Kconfig b/drivers/md/Kconfig
> index 3db222509e44..5370473a0999 100644
> --- a/drivers/md/Kconfig
> +++ b/drivers/md/Kconfig
> @@ -436,6 +436,15 @@ config DM_DELAY
>  
>       If unsure, say N.
>  
> +config DM_DUST
> +     tristate "Bad sector simulation target"
> +     depends on BLK_DEV_DM
> +     ---help---
> +     A target that simulates bad sector behavior, and
> +     limited optimal_io_size behavior.  Useful for testing.
> +
> +     If unsure, say N.
> +
>  config DM_UEVENT
>       bool "DM uevents"
>       depends on BLK_DEV_DM
> diff --git a/drivers/md/Makefile b/drivers/md/Makefile
> index 822f4e8753bc..75bf0391127a 100644
> --- a/drivers/md/Makefile
> +++ b/drivers/md/Makefile
> @@ -48,6 +48,7 @@ obj-$(CONFIG_DM_BUFIO)              += dm-bufio.o
>  obj-$(CONFIG_DM_BIO_PRISON)  += dm-bio-prison.o
>  obj-$(CONFIG_DM_CRYPT)               += dm-crypt.o
>  obj-$(CONFIG_DM_DELAY)               += dm-delay.o
> +obj-$(CONFIG_DM_DUST)                += dm-dust.o
>  obj-$(CONFIG_DM_FLAKEY)              += dm-flakey.o
>  obj-$(CONFIG_DM_MULTIPATH)   += dm-multipath.o dm-round-robin.o
>  obj-$(CONFIG_DM_MULTIPATH_QL)        += dm-queue-length.o
> diff --git a/drivers/md/dm-dust.c b/drivers/md/dm-dust.c
> new file mode 100644
> index 000000000000..94b43d79b186
> --- /dev/null
> +++ b/drivers/md/dm-dust.c
> @@ -0,0 +1,499 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (c) 2018 Red Hat, Inc.
> + *
> + * This is a test "dust" device, which fails reads on specified
> + * sectors, emulating the behavior of a hard disk drive sending
> + * a "Read Medium Error" sense.
> + *
> + */
> +
> +#include <linux/device-mapper.h>
> +#include <linux/module.h>
> +#include <linux/version.h>
> +#include <linux/rbtree.h>
> +
> +#define DM_MSG_PREFIX "dust"
> +
> +struct badblock {
> +     struct rb_node node;
> +     sector_t bb;
> +};
> +
> +struct dust_device {
> +     struct dm_dev *dev;
> +     bool fail_read_on_bb;
> +     struct rb_root badblocklist;
> +     unsigned long long badblock_count;
> +     spinlock_t dust_lock;
> +     unsigned int blksz;
> +     unsigned int sect_per_block;
> +     bool quiet_mode;
> +};
> +
> +struct badblock *dust_rb_search(struct rb_root *root, sector_t blk)
> +{
> +     struct rb_node *node = root->rb_node;
> +
> +     while (node) {
> +             struct badblock *bblk = rb_entry(node, struct badblock, node);
> +
> +             if (bblk->bb > blk)
> +                     node = node->rb_left;
> +             else if (bblk->bb < blk)
> +                     node = node->rb_right;
> +             else
> +                     return bblk;
> +     }
> +     return NULL;
> +}
> +
> +bool dust_rb_insert(struct rb_root *root, struct badblock *new)
> +{
> +     struct badblock *bblk;
> +     struct rb_node **link = &root->rb_node, *parent = NULL;
> +     sector_t value = new->bb;
> +
> +     while (*link) {
> +             parent = *link;
> +             bblk = rb_entry(parent, struct badblock, node);
> +
> +             if (bblk->bb > value)
> +                     link = &(*link)->rb_left;
> +             else if (bblk->bb < value)
> +                     link = &(*link)->rb_right;
> +             else
> +                     return false;
> +     }
> +
> +     rb_link_node(&new->node, parent, link);
> +     rb_insert_color(&new->node, root);
> +     return true;
> +}
> +
> +static int dust_remove_block(struct dust_device *dd, unsigned long long 
> block)
> +{
> +     struct badblock *bblock;
> +     unsigned long flags;
> +
> +     spin_lock_irqsave(&dd->dust_lock, flags);
> +     bblock = dust_rb_search(&dd->badblocklist, block * dd->sect_per_block);
> +
> +     if (bblock == NULL) {
> +             if (!dd->quiet_mode)
> +                     DMERR("removebadblock: block %llu not found in 
> badblocklist",
> +                           block);
> +             spin_unlock_irqrestore(&dd->dust_lock, flags);
> +             return -EINVAL;
> +     }
> +
> +     rb_erase(&bblock->node, &dd->badblocklist);
> +     dd->badblock_count--;
> +     if (!dd->quiet_mode)
> +             DMINFO("removebadblock: badblock removed at block %llu", block);
> +     kfree(bblock);
> +     spin_unlock_irqrestore(&dd->dust_lock, flags);
> +     return 0;
> +}
> +
> +static int dust_add_block(struct dust_device *dd, unsigned long long block)
> +{
> +     struct badblock *bblock;
> +     unsigned long flags;
> +
> +     spin_lock_irqsave(&dd->dust_lock, flags);
> +     bblock = dust_rb_search(&dd->badblocklist, block * dd->sect_per_block);
> +
> +     if (bblock != NULL) {
> +             if (!dd->quiet_mode)
> +                     DMERR("addbadblock: block %llu already in badblocklist",
> +                           block);
> +             spin_unlock_irqrestore(&dd->dust_lock, flags);
> +             return -EINVAL;
> +     }
> +
> +     bblock = kmalloc(sizeof(*bblock), GFP_KERNEL);

You can't do a GFP_KERNEL allocation while holding a spinlock.  I think
it would be better to assume that the user was correctly adding a new
block, do the allocation, and then just call dust_rb_insert() under the
spinlock. dust_rb_insert() will return false if the block is already
inserted 

> +     if (bblock == NULL) {
> +             if (!dd->quiet_mode)
> +                     DMERR("addbadblock: badblock allocation failed");
> +             spin_unlock_irqrestore(&dd->dust_lock, flags);
> +             return -ENOMEM;
> +     }
> +
> +     bblock->bb = block * dd->sect_per_block;
> +     dust_rb_insert(&dd->badblocklist, bblock);
> +     dd->badblock_count++;
> +     if (!dd->quiet_mode)
> +             DMINFO("addbadblock: badblock added at block %llu", block);
> +     spin_unlock_irqrestore(&dd->dust_lock, flags);
> +     return 0;
> +}
> +
> +static int dust_query_block(struct dust_device *dd, unsigned long long block)
> +{
> +     struct badblock *bblock;
> +     unsigned long flags;
> +
> +     spin_lock_irqsave(&dd->dust_lock, flags);
> +     bblock = dust_rb_search(&dd->badblocklist, block * dd->sect_per_block);
> +
> +     if (bblock != NULL)
> +             DMINFO("queryblock: block %llu found in badblocklist", block);
> +     else
> +             DMINFO("queryblock: block %llu not found in badblocklist",
> +                    block);
> +
> +     spin_unlock_irqrestore(&dd->dust_lock, flags);
> +     return 0;
> +}
> +
> +static int dust_map_read(struct dust_device *dd, sector_t thisblock,
> +                      bool fail_read_on_bb)
> +{
> +     unsigned long flags;
> +     struct badblock *bblk;
> +
> +     spin_lock_irqsave(&dd->dust_lock, flags);
> +     bblk = dust_rb_search(&dd->badblocklist, thisblock);

I don't see the benefit of doing dust_rb_search() if fail_read_on_bb
isn't set. It just slows the read without using the result.

> +
> +     if (fail_read_on_bb && bblk) {
> +             spin_unlock_irqrestore(&dd->dust_lock, flags);
> +             return DM_MAPIO_KILL;
> +     }
> +
> +     spin_unlock_irqrestore(&dd->dust_lock, flags);
> +     return DM_MAPIO_REMAPPED;
> +}
> +
> +static int dust_map_write(struct dust_device *dd, sector_t thisblock,
> +                       bool fail_read_on_bb)
> +{
> +     unsigned long flags;
> +     struct badblock *bblk;
> +
> +     spin_lock_irqsave(&dd->dust_lock, flags);
> +     bblk = dust_rb_search(&dd->badblocklist, thisblock);
> +
> +     if (fail_read_on_bb && bblk) {

The same goes for here as well.

> +             rb_erase(&bblk->node, &dd->badblocklist);
> +             dd->badblock_count--;
> +             kfree(bblk);
> +             if (!dd->quiet_mode)
> +                     DMINFO("block %llu removed from badblocklist by write",
> +                             (unsigned long long)thisblock / 
> dd->sect_per_block);
> +     }
> +     spin_unlock_irqrestore(&dd->dust_lock, flags);
> +     return DM_MAPIO_REMAPPED;
> +}
> +
> +static inline void dust_set_biosector(struct bio *bio, sector_t sector)
> +{
> +     bio->bi_iter.bi_sector = sector;
> +}
> +
> +static inline sector_t dust_get_biosector(struct bio *bio)
> +{
> +     return bio->bi_iter.bi_sector;
> +}
> +
> +static int __dust_clear_badblocks(struct rb_root *tree,
> +                               unsigned long long count)
> +{
> +     struct rb_node *node = NULL, *nnode = NULL;
> +
> +     nnode = rb_first(tree);
> +     if (nnode == NULL) {
> +             BUG_ON(count != 0);
> +             return 0;
> +     }
> +
> +     while (nnode) {
> +             node = nnode;
> +             nnode = rb_next(node);
> +             rb_erase(node, tree);
> +             count--;
> +             kfree(node);
> +     }
> +     BUG_ON(count != 0);
> +     BUG_ON(tree->rb_node != NULL);
> +     return 1;
> +}
> +
> +static int dust_clear_badblocks(struct dust_device *dd)
> +{
> +     unsigned long flags;
> +     struct rb_root badblocklist;
> +     unsigned long long badblock_count;
> +
> +     spin_lock_irqsave(&dd->dust_lock, flags);
> +     badblocklist = dd->badblocklist;
> +     badblock_count = dd->badblock_count;
> +     dd->badblocklist = RB_ROOT;
> +     dd->badblock_count = 0;
> +     spin_unlock_irqrestore(&dd->dust_lock, flags);
> +
> +     if (__dust_clear_badblocks(&badblocklist, badblock_count) == 0)
> +             DMINFO("clearbadblocks: no badblocks found");
> +     else
> +             DMINFO("clearbadblocks: badblocks cleared");
> +     return 0;
> +}
> +
> +/*
> + * Target parameters:
> + *
> + * <device_path> <blksz>
> + *
> + * device_path: path to the block device
> + * blksz: block size (valid choices: 512, 4096)
> + */
> +static int dust_ctr(struct dm_target *ti, unsigned int argc, char **argv)
> +{
> +     struct dust_device *dd;
> +     const char *device_path = argv[0];
> +     unsigned int blksz;
> +     unsigned int sect_per_block;
> +
> +     if (argc != 2) {
> +             ti->error = "requires exactly 2 arguments";
> +             return -EINVAL;
> +     }
> +
> +     if (kstrtouint(argv[1], 10, &blksz) || !blksz) {
> +             ti->error =
> +                     "Invalid block size parameter -- please enter 512 or 
> 4096";
> +             return -EINVAL;
> +     }

Why check for !blksz above, when the code only allows 512 and 4096? Why
not just check for those values in the first "if" statement?


-Ben

> +     if (blksz != 512 && blksz != 4096) {
> +             ti->error = "Invalid block size -- please enter 512 or 4096";
> +             return -EINVAL;
> +     }
> +
> +     sect_per_block = (blksz >> SECTOR_SHIFT);
> +
> +     dd = kzalloc(sizeof(struct dust_device), GFP_KERNEL);
> +
> +     if (dd == NULL) {
> +             ti->error = "Cannot allocate context";
> +             return -ENOMEM;
> +     }
> +
> +     if (dm_get_device(ti, device_path, dm_table_get_mode(ti->table),
> +                       &dd->dev)) {
> +             ti->error = "Device lookup failed";
> +             kfree(dd);
> +             return -EINVAL;
> +     }
> +
> +     dd->sect_per_block = sect_per_block;
> +     dd->blksz = blksz;
> +
> +     // Whether to fail a read on a "bad" block.
> +     // Defaults to false; enabled later by message.
> +     dd->fail_read_on_bb = false;
> +
> +     // Bad block list rbtree.
> +     // Just initialize for now.
> +     dd->badblocklist = RB_ROOT;
> +     dd->badblock_count = 0;
> +     spin_lock_init(&dd->dust_lock);
> +
> +     dd->quiet_mode = false;
> +
> +     BUG_ON(dm_set_target_max_io_len(ti, dd->sect_per_block) != 0);
> +
> +     ti->num_discard_bios = 1;
> +     ti->num_flush_bios = 1;
> +     ti->private = dd;
> +     return 0;
> +}
> +
> +static void dust_dtr(struct dm_target *ti)
> +{
> +     struct dust_device *dd = ti->private;
> +
> +     __dust_clear_badblocks(&dd->badblocklist, dd->badblock_count);
> +     dm_put_device(ti, dd->dev);
> +     kfree(dd);
> +}
> +
> +static void dust_io_hints(struct dm_target *ti, struct queue_limits *limits)
> +{
> +     struct dust_device *dd = ti->private;
> +
> +     limits->logical_block_size = dd->blksz;
> +     limits->physical_block_size = dd->blksz;
> +
> +     // The minimum io size for random io
> +     blk_limits_io_min(limits, dd->blksz);
> +     // The optimal io size for streamed/sequential io
> +     blk_limits_io_opt(limits, dd->blksz);
> +}
> +
> +static int dust_map(struct dm_target *ti, struct bio *bio)
> +{
> +     struct dust_device *dd;
> +     sector_t thisblock;
> +     int ret;
> +
> +     dd = ti->private;
> +     bio_set_dev(bio, dd->dev->bdev);
> +     dust_set_biosector(bio, dm_target_offset(ti, dust_get_biosector(bio)));
> +     thisblock = dust_get_biosector(bio);
> +
> +     if (bio_data_dir(bio) == READ)
> +             ret = dust_map_read(dd, thisblock, dd->fail_read_on_bb);
> +     if (bio_data_dir(bio) == WRITE)
> +             ret = dust_map_write(dd, thisblock, dd->fail_read_on_bb);
> +
> +     return ret;
> +}
> +
> +static int dust_message(struct dm_target *ti, unsigned int argc, char **argv,
> +                char *result_buf, unsigned int maxlen)
> +{
> +     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;
> +     unsigned long long tmp, block;
> +     unsigned long flags;
> +     char dummy;
> +
> +     if (argc == 1) {
> +             if (!strcasecmp(argv[0], "addbadblock") ||
> +                 !strcasecmp(argv[0], "removebadblock") ||
> +                 !strcasecmp(argv[0], "queryblock")) {
> +                     DMERR("%s requires an 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;
> +             } else if (!strcasecmp(argv[0], "enable")) {
> +                     DMINFO("enabling read failures on bad sectors");
> +                     dd->fail_read_on_bb = true;
> +                     result = 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;
> +             } else if (!strcasecmp(argv[0], "clearbadblocks")) {
> +                     result = dust_clear_badblocks(dd);
> +             } else if (!strcasecmp(argv[0], "quiet")) {
> +                     if (!dd->quiet_mode)
> +                             dd->quiet_mode = true;
> +                     else
> +                             dd->quiet_mode = false;
> +                     result = 0;
> +             } else {
> +                     invalid_msg = true;
> +             }
> +     } else if (argc == 2) {
> +             if (sscanf(argv[1], "%llu%c", &tmp, &dummy) != 1)
> +                     return result;
> +
> +             block = tmp;
> +
> +             if (block > size / dd->sect_per_block || block < 0) {
> +                     DMERR("selected block value out of range");
> +                     return result;
> +             }
> +
> +             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);
> +             else
> +                     invalid_msg = true;
> +
> +     } else {
> +             DMERR("invalid number of arguments '%d'", argc);
> +     }
> +
> +     if (invalid_msg)
> +             DMERR("unrecognized dmsetup message '%s' received", argv[0]);
> +
> +     return result;
> +}
> +
> +static void dust_status(struct dm_target *ti, status_type_t type,
> +                unsigned int status_flags, char *result, unsigned int maxlen)
> +{
> +     struct dust_device *dd = ti->private;
> +     unsigned int sz = 0; // used by the DMEMIT macro
> +
> +     switch (type) {
> +     case STATUSTYPE_INFO:
> +             DMEMIT("%s %s %s", dd->dev->name,
> +                    dd->fail_read_on_bb ? "fail_read_on_bad_block" :
> +                                          "bypass",
> +                    dd->quiet_mode ? "quiet" : "verbose");
> +             break;
> +
> +     case STATUSTYPE_TABLE:
> +             DMEMIT("%s %u", dd->dev->name, dd->blksz);
> +             break;
> +     }
> +}
> +
> +static int dust_prepare_ioctl(struct dm_target *ti, struct block_device 
> **bdev)
> +{
> +     struct dust_device *dd = ti->private;
> +     struct dm_dev *dev = dd->dev;
> +
> +     *bdev = dev->bdev;
> +
> +     // Only pass ioctls through if the device sizes match exactly.
> +     if (ti->len != i_size_read(dev->bdev->bd_inode) >> SECTOR_SHIFT)
> +             return 1;
> +
> +     return 0;
> +}
> +
> +static int dust_iterate_devices(struct dm_target *ti, 
> iterate_devices_callout_fn fn,
> +                        void *data)
> +{
> +     struct dust_device *dd = ti->private;
> +
> +     return fn(ti, dd->dev, 0, ti->len, data);
> +}
> +
> +static struct target_type dust_target = {
> +     .name = "dust",
> +     .version = { 1, 0, 0 },
> +     .module = THIS_MODULE,
> +     .ctr = dust_ctr,
> +     .dtr = dust_dtr,
> +     .iterate_devices = dust_iterate_devices,
> +     .io_hints = dust_io_hints,
> +     .map = dust_map,
> +     .message = dust_message,
> +     .status = dust_status,
> +     .prepare_ioctl = dust_prepare_ioctl,
> +};
> +
> +int __init dm_dust_init(void)
> +{
> +     int result = dm_register_target(&dust_target);
> +
> +     if (result < 0)
> +             DMERR("dm_register_target failed %d", result);
> +
> +     return result;
> +}
> +
> +void __exit dm_dust_exit(void)
> +{
> +     dm_unregister_target(&dust_target);
> +}
> +
> +module_init(dm_dust_init);
> +module_exit(dm_dust_exit);
> +
> +MODULE_DESCRIPTION(DM_NAME " dust testing device");
> +MODULE_AUTHOR("Red Hat, Inc.");
> +MODULE_LICENSE("GPL");
> -- 
> 2.17.2

--
dm-devel mailing list
dm-devel@redhat.com
https://www.redhat.com/mailman/listinfo/dm-devel

Reply via email to