Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0
0002 /*
0003  * Copyright (c) 2018 Red Hat, Inc.
0004  *
0005  * This is a test "dust" device, which fails reads on specified
0006  * sectors, emulating the behavior of a hard disk drive sending
0007  * a "Read Medium Error" sense.
0008  *
0009  */
0010 
0011 #include <linux/device-mapper.h>
0012 #include <linux/module.h>
0013 #include <linux/rbtree.h>
0014 
0015 #define DM_MSG_PREFIX "dust"
0016 
0017 struct badblock {
0018     struct rb_node node;
0019     sector_t bb;
0020     unsigned char wr_fail_cnt;
0021 };
0022 
0023 struct dust_device {
0024     struct dm_dev *dev;
0025     struct rb_root badblocklist;
0026     unsigned long long badblock_count;
0027     spinlock_t dust_lock;
0028     unsigned int blksz;
0029     int sect_per_block_shift;
0030     unsigned int sect_per_block;
0031     sector_t start;
0032     bool fail_read_on_bb:1;
0033     bool quiet_mode:1;
0034 };
0035 
0036 static struct badblock *dust_rb_search(struct rb_root *root, sector_t blk)
0037 {
0038     struct rb_node *node = root->rb_node;
0039 
0040     while (node) {
0041         struct badblock *bblk = rb_entry(node, struct badblock, node);
0042 
0043         if (bblk->bb > blk)
0044             node = node->rb_left;
0045         else if (bblk->bb < blk)
0046             node = node->rb_right;
0047         else
0048             return bblk;
0049     }
0050 
0051     return NULL;
0052 }
0053 
0054 static bool dust_rb_insert(struct rb_root *root, struct badblock *new)
0055 {
0056     struct badblock *bblk;
0057     struct rb_node **link = &root->rb_node, *parent = NULL;
0058     sector_t value = new->bb;
0059 
0060     while (*link) {
0061         parent = *link;
0062         bblk = rb_entry(parent, struct badblock, node);
0063 
0064         if (bblk->bb > value)
0065             link = &(*link)->rb_left;
0066         else if (bblk->bb < value)
0067             link = &(*link)->rb_right;
0068         else
0069             return false;
0070     }
0071 
0072     rb_link_node(&new->node, parent, link);
0073     rb_insert_color(&new->node, root);
0074 
0075     return true;
0076 }
0077 
0078 static int dust_remove_block(struct dust_device *dd, unsigned long long block)
0079 {
0080     struct badblock *bblock;
0081     unsigned long flags;
0082 
0083     spin_lock_irqsave(&dd->dust_lock, flags);
0084     bblock = dust_rb_search(&dd->badblocklist, block);
0085 
0086     if (bblock == NULL) {
0087         if (!dd->quiet_mode) {
0088             DMERR("%s: block %llu not found in badblocklist",
0089                   __func__, block);
0090         }
0091         spin_unlock_irqrestore(&dd->dust_lock, flags);
0092         return -EINVAL;
0093     }
0094 
0095     rb_erase(&bblock->node, &dd->badblocklist);
0096     dd->badblock_count--;
0097     if (!dd->quiet_mode)
0098         DMINFO("%s: badblock removed at block %llu", __func__, block);
0099     kfree(bblock);
0100     spin_unlock_irqrestore(&dd->dust_lock, flags);
0101 
0102     return 0;
0103 }
0104 
0105 static int dust_add_block(struct dust_device *dd, unsigned long long block,
0106               unsigned char wr_fail_cnt)
0107 {
0108     struct badblock *bblock;
0109     unsigned long flags;
0110 
0111     bblock = kmalloc(sizeof(*bblock), GFP_KERNEL);
0112     if (bblock == NULL) {
0113         if (!dd->quiet_mode)
0114             DMERR("%s: badblock allocation failed", __func__);
0115         return -ENOMEM;
0116     }
0117 
0118     spin_lock_irqsave(&dd->dust_lock, flags);
0119     bblock->bb = block;
0120     bblock->wr_fail_cnt = wr_fail_cnt;
0121     if (!dust_rb_insert(&dd->badblocklist, bblock)) {
0122         if (!dd->quiet_mode) {
0123             DMERR("%s: block %llu already in badblocklist",
0124                   __func__, block);
0125         }
0126         spin_unlock_irqrestore(&dd->dust_lock, flags);
0127         kfree(bblock);
0128         return -EINVAL;
0129     }
0130 
0131     dd->badblock_count++;
0132     if (!dd->quiet_mode) {
0133         DMINFO("%s: badblock added at block %llu with write fail count %u",
0134                __func__, block, wr_fail_cnt);
0135     }
0136     spin_unlock_irqrestore(&dd->dust_lock, flags);
0137 
0138     return 0;
0139 }
0140 
0141 static int dust_query_block(struct dust_device *dd, unsigned long long block, char *result,
0142                 unsigned int maxlen, unsigned int *sz_ptr)
0143 {
0144     struct badblock *bblock;
0145     unsigned long flags;
0146     unsigned int sz = *sz_ptr;
0147 
0148     spin_lock_irqsave(&dd->dust_lock, flags);
0149     bblock = dust_rb_search(&dd->badblocklist, block);
0150     if (bblock != NULL)
0151         DMEMIT("%s: block %llu found in badblocklist", __func__, block);
0152     else
0153         DMEMIT("%s: block %llu not found in badblocklist", __func__, block);
0154     spin_unlock_irqrestore(&dd->dust_lock, flags);
0155 
0156     return 1;
0157 }
0158 
0159 static int __dust_map_read(struct dust_device *dd, sector_t thisblock)
0160 {
0161     struct badblock *bblk = dust_rb_search(&dd->badblocklist, thisblock);
0162 
0163     if (bblk)
0164         return DM_MAPIO_KILL;
0165 
0166     return DM_MAPIO_REMAPPED;
0167 }
0168 
0169 static int dust_map_read(struct dust_device *dd, sector_t thisblock,
0170              bool fail_read_on_bb)
0171 {
0172     unsigned long flags;
0173     int r = DM_MAPIO_REMAPPED;
0174 
0175     if (fail_read_on_bb) {
0176         thisblock >>= dd->sect_per_block_shift;
0177         spin_lock_irqsave(&dd->dust_lock, flags);
0178         r = __dust_map_read(dd, thisblock);
0179         spin_unlock_irqrestore(&dd->dust_lock, flags);
0180     }
0181 
0182     return r;
0183 }
0184 
0185 static int __dust_map_write(struct dust_device *dd, sector_t thisblock)
0186 {
0187     struct badblock *bblk = dust_rb_search(&dd->badblocklist, thisblock);
0188 
0189     if (bblk && bblk->wr_fail_cnt > 0) {
0190         bblk->wr_fail_cnt--;
0191         return DM_MAPIO_KILL;
0192     }
0193 
0194     if (bblk) {
0195         rb_erase(&bblk->node, &dd->badblocklist);
0196         dd->badblock_count--;
0197         kfree(bblk);
0198         if (!dd->quiet_mode) {
0199             sector_div(thisblock, dd->sect_per_block);
0200             DMINFO("block %llu removed from badblocklist by write",
0201                    (unsigned long long)thisblock);
0202         }
0203     }
0204 
0205     return DM_MAPIO_REMAPPED;
0206 }
0207 
0208 static int dust_map_write(struct dust_device *dd, sector_t thisblock,
0209               bool fail_read_on_bb)
0210 {
0211     unsigned long flags;
0212     int r = DM_MAPIO_REMAPPED;
0213 
0214     if (fail_read_on_bb) {
0215         thisblock >>= dd->sect_per_block_shift;
0216         spin_lock_irqsave(&dd->dust_lock, flags);
0217         r = __dust_map_write(dd, thisblock);
0218         spin_unlock_irqrestore(&dd->dust_lock, flags);
0219     }
0220 
0221     return r;
0222 }
0223 
0224 static int dust_map(struct dm_target *ti, struct bio *bio)
0225 {
0226     struct dust_device *dd = ti->private;
0227     int r;
0228 
0229     bio_set_dev(bio, dd->dev->bdev);
0230     bio->bi_iter.bi_sector = dd->start + dm_target_offset(ti, bio->bi_iter.bi_sector);
0231 
0232     if (bio_data_dir(bio) == READ)
0233         r = dust_map_read(dd, bio->bi_iter.bi_sector, dd->fail_read_on_bb);
0234     else
0235         r = dust_map_write(dd, bio->bi_iter.bi_sector, dd->fail_read_on_bb);
0236 
0237     return r;
0238 }
0239 
0240 static bool __dust_clear_badblocks(struct rb_root *tree,
0241                    unsigned long long count)
0242 {
0243     struct rb_node *node = NULL, *nnode = NULL;
0244 
0245     nnode = rb_first(tree);
0246     if (nnode == NULL) {
0247         BUG_ON(count != 0);
0248         return false;
0249     }
0250 
0251     while (nnode) {
0252         node = nnode;
0253         nnode = rb_next(node);
0254         rb_erase(node, tree);
0255         count--;
0256         kfree(node);
0257     }
0258     BUG_ON(count != 0);
0259     BUG_ON(tree->rb_node != NULL);
0260 
0261     return true;
0262 }
0263 
0264 static int dust_clear_badblocks(struct dust_device *dd, char *result, unsigned int maxlen,
0265                 unsigned int *sz_ptr)
0266 {
0267     unsigned long flags;
0268     struct rb_root badblocklist;
0269     unsigned long long badblock_count;
0270     unsigned int sz = *sz_ptr;
0271 
0272     spin_lock_irqsave(&dd->dust_lock, flags);
0273     badblocklist = dd->badblocklist;
0274     badblock_count = dd->badblock_count;
0275     dd->badblocklist = RB_ROOT;
0276     dd->badblock_count = 0;
0277     spin_unlock_irqrestore(&dd->dust_lock, flags);
0278 
0279     if (!__dust_clear_badblocks(&badblocklist, badblock_count))
0280         DMEMIT("%s: no badblocks found", __func__);
0281     else
0282         DMEMIT("%s: badblocks cleared", __func__);
0283 
0284     return 1;
0285 }
0286 
0287 static int dust_list_badblocks(struct dust_device *dd, char *result, unsigned int maxlen,
0288                 unsigned int *sz_ptr)
0289 {
0290     unsigned long flags;
0291     struct rb_root badblocklist;
0292     struct rb_node *node;
0293     struct badblock *bblk;
0294     unsigned int sz = *sz_ptr;
0295     unsigned long long num = 0;
0296 
0297     spin_lock_irqsave(&dd->dust_lock, flags);
0298     badblocklist = dd->badblocklist;
0299     for (node = rb_first(&badblocklist); node; node = rb_next(node)) {
0300         bblk = rb_entry(node, struct badblock, node);
0301         DMEMIT("%llu\n", bblk->bb);
0302         num++;
0303     }
0304 
0305     spin_unlock_irqrestore(&dd->dust_lock, flags);
0306     if (!num)
0307         DMEMIT("No blocks in badblocklist");
0308 
0309     return 1;
0310 }
0311 
0312 /*
0313  * Target parameters:
0314  *
0315  * <device_path> <offset> <blksz>
0316  *
0317  * device_path: path to the block device
0318  * offset: offset to data area from start of device_path
0319  * blksz: block size (minimum 512, maximum 1073741824, must be a power of 2)
0320  */
0321 static int dust_ctr(struct dm_target *ti, unsigned int argc, char **argv)
0322 {
0323     struct dust_device *dd;
0324     unsigned long long tmp;
0325     char dummy;
0326     unsigned int blksz;
0327     unsigned int sect_per_block;
0328     sector_t DUST_MAX_BLKSZ_SECTORS = 2097152;
0329     sector_t max_block_sectors = min(ti->len, DUST_MAX_BLKSZ_SECTORS);
0330 
0331     if (argc != 3) {
0332         ti->error = "Invalid argument count";
0333         return -EINVAL;
0334     }
0335 
0336     if (kstrtouint(argv[2], 10, &blksz) || !blksz) {
0337         ti->error = "Invalid block size parameter";
0338         return -EINVAL;
0339     }
0340 
0341     if (blksz < 512) {
0342         ti->error = "Block size must be at least 512";
0343         return -EINVAL;
0344     }
0345 
0346     if (!is_power_of_2(blksz)) {
0347         ti->error = "Block size must be a power of 2";
0348         return -EINVAL;
0349     }
0350 
0351     if (to_sector(blksz) > max_block_sectors) {
0352         ti->error = "Block size is too large";
0353         return -EINVAL;
0354     }
0355 
0356     sect_per_block = (blksz >> SECTOR_SHIFT);
0357 
0358     if (sscanf(argv[1], "%llu%c", &tmp, &dummy) != 1 || tmp != (sector_t)tmp) {
0359         ti->error = "Invalid device offset sector";
0360         return -EINVAL;
0361     }
0362 
0363     dd = kzalloc(sizeof(struct dust_device), GFP_KERNEL);
0364     if (dd == NULL) {
0365         ti->error = "Cannot allocate context";
0366         return -ENOMEM;
0367     }
0368 
0369     if (dm_get_device(ti, argv[0], dm_table_get_mode(ti->table), &dd->dev)) {
0370         ti->error = "Device lookup failed";
0371         kfree(dd);
0372         return -EINVAL;
0373     }
0374 
0375     dd->sect_per_block = sect_per_block;
0376     dd->blksz = blksz;
0377     dd->start = tmp;
0378 
0379     dd->sect_per_block_shift = __ffs(sect_per_block);
0380 
0381     /*
0382      * Whether to fail a read on a "bad" block.
0383      * Defaults to false; enabled later by message.
0384      */
0385     dd->fail_read_on_bb = false;
0386 
0387     /*
0388      * Initialize bad block list rbtree.
0389      */
0390     dd->badblocklist = RB_ROOT;
0391     dd->badblock_count = 0;
0392     spin_lock_init(&dd->dust_lock);
0393 
0394     dd->quiet_mode = false;
0395 
0396     BUG_ON(dm_set_target_max_io_len(ti, dd->sect_per_block) != 0);
0397 
0398     ti->num_discard_bios = 1;
0399     ti->num_flush_bios = 1;
0400     ti->private = dd;
0401 
0402     return 0;
0403 }
0404 
0405 static void dust_dtr(struct dm_target *ti)
0406 {
0407     struct dust_device *dd = ti->private;
0408 
0409     __dust_clear_badblocks(&dd->badblocklist, dd->badblock_count);
0410     dm_put_device(ti, dd->dev);
0411     kfree(dd);
0412 }
0413 
0414 static int dust_message(struct dm_target *ti, unsigned int argc, char **argv,
0415             char *result, unsigned int maxlen)
0416 {
0417     struct dust_device *dd = ti->private;
0418     sector_t size = bdev_nr_sectors(dd->dev->bdev);
0419     bool invalid_msg = false;
0420     int r = -EINVAL;
0421     unsigned long long tmp, block;
0422     unsigned char wr_fail_cnt;
0423     unsigned int tmp_ui;
0424     unsigned long flags;
0425     unsigned int sz = 0;
0426     char dummy;
0427 
0428     if (argc == 1) {
0429         if (!strcasecmp(argv[0], "addbadblock") ||
0430             !strcasecmp(argv[0], "removebadblock") ||
0431             !strcasecmp(argv[0], "queryblock")) {
0432             DMERR("%s requires an additional argument", argv[0]);
0433         } else if (!strcasecmp(argv[0], "disable")) {
0434             DMINFO("disabling read failures on bad sectors");
0435             dd->fail_read_on_bb = false;
0436             r = 0;
0437         } else if (!strcasecmp(argv[0], "enable")) {
0438             DMINFO("enabling read failures on bad sectors");
0439             dd->fail_read_on_bb = true;
0440             r = 0;
0441         } else if (!strcasecmp(argv[0], "countbadblocks")) {
0442             spin_lock_irqsave(&dd->dust_lock, flags);
0443             DMEMIT("countbadblocks: %llu badblock(s) found",
0444                    dd->badblock_count);
0445             spin_unlock_irqrestore(&dd->dust_lock, flags);
0446             r = 1;
0447         } else if (!strcasecmp(argv[0], "clearbadblocks")) {
0448             r = dust_clear_badblocks(dd, result, maxlen, &sz);
0449         } else if (!strcasecmp(argv[0], "quiet")) {
0450             if (!dd->quiet_mode)
0451                 dd->quiet_mode = true;
0452             else
0453                 dd->quiet_mode = false;
0454             r = 0;
0455         } else if (!strcasecmp(argv[0], "listbadblocks")) {
0456             r = dust_list_badblocks(dd, result, maxlen, &sz);
0457         } else {
0458             invalid_msg = true;
0459         }
0460     } else if (argc == 2) {
0461         if (sscanf(argv[1], "%llu%c", &tmp, &dummy) != 1)
0462             return r;
0463 
0464         block = tmp;
0465         sector_div(size, dd->sect_per_block);
0466         if (block > size) {
0467             DMERR("selected block value out of range");
0468             return r;
0469         }
0470 
0471         if (!strcasecmp(argv[0], "addbadblock"))
0472             r = dust_add_block(dd, block, 0);
0473         else if (!strcasecmp(argv[0], "removebadblock"))
0474             r = dust_remove_block(dd, block);
0475         else if (!strcasecmp(argv[0], "queryblock"))
0476             r = dust_query_block(dd, block, result, maxlen, &sz);
0477         else
0478             invalid_msg = true;
0479 
0480     } else if (argc == 3) {
0481         if (sscanf(argv[1], "%llu%c", &tmp, &dummy) != 1)
0482             return r;
0483 
0484         if (sscanf(argv[2], "%u%c", &tmp_ui, &dummy) != 1)
0485             return r;
0486 
0487         block = tmp;
0488         if (tmp_ui > 255) {
0489             DMERR("selected write fail count out of range");
0490             return r;
0491         }
0492         wr_fail_cnt = tmp_ui;
0493         sector_div(size, dd->sect_per_block);
0494         if (block > size) {
0495             DMERR("selected block value out of range");
0496             return r;
0497         }
0498 
0499         if (!strcasecmp(argv[0], "addbadblock"))
0500             r = dust_add_block(dd, block, wr_fail_cnt);
0501         else
0502             invalid_msg = true;
0503 
0504     } else
0505         DMERR("invalid number of arguments '%d'", argc);
0506 
0507     if (invalid_msg)
0508         DMERR("unrecognized message '%s' received", argv[0]);
0509 
0510     return r;
0511 }
0512 
0513 static void dust_status(struct dm_target *ti, status_type_t type,
0514             unsigned int status_flags, char *result, unsigned int maxlen)
0515 {
0516     struct dust_device *dd = ti->private;
0517     unsigned int sz = 0;
0518 
0519     switch (type) {
0520     case STATUSTYPE_INFO:
0521         DMEMIT("%s %s %s", dd->dev->name,
0522                dd->fail_read_on_bb ? "fail_read_on_bad_block" : "bypass",
0523                dd->quiet_mode ? "quiet" : "verbose");
0524         break;
0525 
0526     case STATUSTYPE_TABLE:
0527         DMEMIT("%s %llu %u", dd->dev->name,
0528                (unsigned long long)dd->start, dd->blksz);
0529         break;
0530 
0531     case STATUSTYPE_IMA:
0532         *result = '\0';
0533         break;
0534     }
0535 }
0536 
0537 static int dust_prepare_ioctl(struct dm_target *ti, struct block_device **bdev)
0538 {
0539     struct dust_device *dd = ti->private;
0540     struct dm_dev *dev = dd->dev;
0541 
0542     *bdev = dev->bdev;
0543 
0544     /*
0545      * Only pass ioctls through if the device sizes match exactly.
0546      */
0547     if (dd->start || ti->len != bdev_nr_sectors(dev->bdev))
0548         return 1;
0549 
0550     return 0;
0551 }
0552 
0553 static int dust_iterate_devices(struct dm_target *ti, iterate_devices_callout_fn fn,
0554                 void *data)
0555 {
0556     struct dust_device *dd = ti->private;
0557 
0558     return fn(ti, dd->dev, dd->start, ti->len, data);
0559 }
0560 
0561 static struct target_type dust_target = {
0562     .name = "dust",
0563     .version = {1, 0, 0},
0564     .module = THIS_MODULE,
0565     .ctr = dust_ctr,
0566     .dtr = dust_dtr,
0567     .iterate_devices = dust_iterate_devices,
0568     .map = dust_map,
0569     .message = dust_message,
0570     .status = dust_status,
0571     .prepare_ioctl = dust_prepare_ioctl,
0572 };
0573 
0574 static int __init dm_dust_init(void)
0575 {
0576     int r = dm_register_target(&dust_target);
0577 
0578     if (r < 0)
0579         DMERR("dm_register_target failed %d", r);
0580 
0581     return r;
0582 }
0583 
0584 static void __exit dm_dust_exit(void)
0585 {
0586     dm_unregister_target(&dust_target);
0587 }
0588 
0589 module_init(dm_dust_init);
0590 module_exit(dm_dust_exit);
0591 
0592 MODULE_DESCRIPTION(DM_NAME " dust test target");
0593 MODULE_AUTHOR("Bryan Gurney <dm-devel@redhat.com>");
0594 MODULE_LICENSE("GPL");