1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * Copyright (c) 2018 Red Hat, Inc. 4 * 5 * This is a test "dust" device, which fails reads on specified 6 * sectors, emulating the behavior of a hard disk drive sending 7 * a "Read Medium Error" sense. 8 * 9 */ 10 11 #include <linux/device-mapper.h> 12 #include <linux/module.h> 13 #include <linux/rbtree.h> 14 15 #define DM_MSG_PREFIX "dust" 16 17 struct badblock { 18 struct rb_node node; 19 sector_t bb; 20 }; 21 22 struct dust_device { 23 struct dm_dev *dev; 24 struct rb_root badblocklist; 25 unsigned long long badblock_count; 26 spinlock_t dust_lock; 27 unsigned int blksz; 28 int sect_per_block_shift; 29 unsigned int sect_per_block; 30 sector_t start; 31 bool fail_read_on_bb:1; 32 bool quiet_mode:1; 33 }; 34 35 static struct badblock *dust_rb_search(struct rb_root *root, sector_t blk) 36 { 37 struct rb_node *node = root->rb_node; 38 39 while (node) { 40 struct badblock *bblk = rb_entry(node, struct badblock, node); 41 42 if (bblk->bb > blk) 43 node = node->rb_left; 44 else if (bblk->bb < blk) 45 node = node->rb_right; 46 else 47 return bblk; 48 } 49 50 return NULL; 51 } 52 53 static bool dust_rb_insert(struct rb_root *root, struct badblock *new) 54 { 55 struct badblock *bblk; 56 struct rb_node **link = &root->rb_node, *parent = NULL; 57 sector_t value = new->bb; 58 59 while (*link) { 60 parent = *link; 61 bblk = rb_entry(parent, struct badblock, node); 62 63 if (bblk->bb > value) 64 link = &(*link)->rb_left; 65 else if (bblk->bb < value) 66 link = &(*link)->rb_right; 67 else 68 return false; 69 } 70 71 rb_link_node(&new->node, parent, link); 72 rb_insert_color(&new->node, root); 73 74 return true; 75 } 76 77 static int dust_remove_block(struct dust_device *dd, unsigned long long block) 78 { 79 struct badblock *bblock; 80 unsigned long flags; 81 82 spin_lock_irqsave(&dd->dust_lock, flags); 83 bblock = dust_rb_search(&dd->badblocklist, block); 84 85 if (bblock == NULL) { 86 if (!dd->quiet_mode) { 87 DMERR("%s: block %llu not found in badblocklist", 88 __func__, block); 89 } 90 spin_unlock_irqrestore(&dd->dust_lock, flags); 91 return -EINVAL; 92 } 93 94 rb_erase(&bblock->node, &dd->badblocklist); 95 dd->badblock_count--; 96 if (!dd->quiet_mode) 97 DMINFO("%s: badblock removed at block %llu", __func__, block); 98 kfree(bblock); 99 spin_unlock_irqrestore(&dd->dust_lock, flags); 100 101 return 0; 102 } 103 104 static int dust_add_block(struct dust_device *dd, unsigned long long block) 105 { 106 struct badblock *bblock; 107 unsigned long flags; 108 109 bblock = kmalloc(sizeof(*bblock), GFP_KERNEL); 110 if (bblock == NULL) { 111 if (!dd->quiet_mode) 112 DMERR("%s: badblock allocation failed", __func__); 113 return -ENOMEM; 114 } 115 116 spin_lock_irqsave(&dd->dust_lock, flags); 117 bblock->bb = block; 118 if (!dust_rb_insert(&dd->badblocklist, bblock)) { 119 if (!dd->quiet_mode) { 120 DMERR("%s: block %llu already in badblocklist", 121 __func__, block); 122 } 123 spin_unlock_irqrestore(&dd->dust_lock, flags); 124 kfree(bblock); 125 return -EINVAL; 126 } 127 128 dd->badblock_count++; 129 if (!dd->quiet_mode) 130 DMINFO("%s: badblock added at block %llu", __func__, block); 131 spin_unlock_irqrestore(&dd->dust_lock, flags); 132 133 return 0; 134 } 135 136 static int dust_query_block(struct dust_device *dd, unsigned long long block) 137 { 138 struct badblock *bblock; 139 unsigned long flags; 140 141 spin_lock_irqsave(&dd->dust_lock, flags); 142 bblock = dust_rb_search(&dd->badblocklist, block); 143 if (bblock != NULL) 144 DMINFO("%s: block %llu found in badblocklist", __func__, block); 145 else 146 DMINFO("%s: block %llu not found in badblocklist", __func__, block); 147 spin_unlock_irqrestore(&dd->dust_lock, flags); 148 149 return 0; 150 } 151 152 static int __dust_map_read(struct dust_device *dd, sector_t thisblock) 153 { 154 struct badblock *bblk = dust_rb_search(&dd->badblocklist, thisblock); 155 156 if (bblk) 157 return DM_MAPIO_KILL; 158 159 return DM_MAPIO_REMAPPED; 160 } 161 162 static int dust_map_read(struct dust_device *dd, sector_t thisblock, 163 bool fail_read_on_bb) 164 { 165 unsigned long flags; 166 int ret = DM_MAPIO_REMAPPED; 167 168 if (fail_read_on_bb) { 169 thisblock >>= dd->sect_per_block_shift; 170 spin_lock_irqsave(&dd->dust_lock, flags); 171 ret = __dust_map_read(dd, thisblock); 172 spin_unlock_irqrestore(&dd->dust_lock, flags); 173 } 174 175 return ret; 176 } 177 178 static void __dust_map_write(struct dust_device *dd, sector_t thisblock) 179 { 180 struct badblock *bblk = dust_rb_search(&dd->badblocklist, thisblock); 181 182 if (bblk) { 183 rb_erase(&bblk->node, &dd->badblocklist); 184 dd->badblock_count--; 185 kfree(bblk); 186 if (!dd->quiet_mode) { 187 sector_div(thisblock, dd->sect_per_block); 188 DMINFO("block %llu removed from badblocklist by write", 189 (unsigned long long)thisblock); 190 } 191 } 192 } 193 194 static int dust_map_write(struct dust_device *dd, sector_t thisblock, 195 bool fail_read_on_bb) 196 { 197 unsigned long flags; 198 199 if (fail_read_on_bb) { 200 thisblock >>= dd->sect_per_block_shift; 201 spin_lock_irqsave(&dd->dust_lock, flags); 202 __dust_map_write(dd, thisblock); 203 spin_unlock_irqrestore(&dd->dust_lock, flags); 204 } 205 206 return DM_MAPIO_REMAPPED; 207 } 208 209 static int dust_map(struct dm_target *ti, struct bio *bio) 210 { 211 struct dust_device *dd = ti->private; 212 int ret; 213 214 bio_set_dev(bio, dd->dev->bdev); 215 bio->bi_iter.bi_sector = dd->start + dm_target_offset(ti, bio->bi_iter.bi_sector); 216 217 if (bio_data_dir(bio) == READ) 218 ret = dust_map_read(dd, bio->bi_iter.bi_sector, dd->fail_read_on_bb); 219 else 220 ret = dust_map_write(dd, bio->bi_iter.bi_sector, dd->fail_read_on_bb); 221 222 return ret; 223 } 224 225 static bool __dust_clear_badblocks(struct rb_root *tree, 226 unsigned long long count) 227 { 228 struct rb_node *node = NULL, *nnode = NULL; 229 230 nnode = rb_first(tree); 231 if (nnode == NULL) { 232 BUG_ON(count != 0); 233 return false; 234 } 235 236 while (nnode) { 237 node = nnode; 238 nnode = rb_next(node); 239 rb_erase(node, tree); 240 count--; 241 kfree(node); 242 } 243 BUG_ON(count != 0); 244 BUG_ON(tree->rb_node != NULL); 245 246 return true; 247 } 248 249 static int dust_clear_badblocks(struct dust_device *dd) 250 { 251 unsigned long flags; 252 struct rb_root badblocklist; 253 unsigned long long badblock_count; 254 255 spin_lock_irqsave(&dd->dust_lock, flags); 256 badblocklist = dd->badblocklist; 257 badblock_count = dd->badblock_count; 258 dd->badblocklist = RB_ROOT; 259 dd->badblock_count = 0; 260 spin_unlock_irqrestore(&dd->dust_lock, flags); 261 262 if (!__dust_clear_badblocks(&badblocklist, badblock_count)) 263 DMINFO("%s: no badblocks found", __func__); 264 else 265 DMINFO("%s: badblocks cleared", __func__); 266 267 return 0; 268 } 269 270 /* 271 * Target parameters: 272 * 273 * <device_path> <offset> <blksz> 274 * 275 * device_path: path to the block device 276 * offset: offset to data area from start of device_path 277 * blksz: block size (minimum 512, maximum 1073741824, must be a power of 2) 278 */ 279 static int dust_ctr(struct dm_target *ti, unsigned int argc, char **argv) 280 { 281 struct dust_device *dd; 282 unsigned long long tmp; 283 char dummy; 284 unsigned int blksz; 285 unsigned int sect_per_block; 286 sector_t DUST_MAX_BLKSZ_SECTORS = 2097152; 287 sector_t max_block_sectors = min(ti->len, DUST_MAX_BLKSZ_SECTORS); 288 289 if (argc != 3) { 290 ti->error = "Invalid argument count"; 291 return -EINVAL; 292 } 293 294 if (kstrtouint(argv[2], 10, &blksz) || !blksz) { 295 ti->error = "Invalid block size parameter"; 296 return -EINVAL; 297 } 298 299 if (blksz < 512) { 300 ti->error = "Block size must be at least 512"; 301 return -EINVAL; 302 } 303 304 if (!is_power_of_2(blksz)) { 305 ti->error = "Block size must be a power of 2"; 306 return -EINVAL; 307 } 308 309 if (to_sector(blksz) > max_block_sectors) { 310 ti->error = "Block size is too large"; 311 return -EINVAL; 312 } 313 314 sect_per_block = (blksz >> SECTOR_SHIFT); 315 316 if (sscanf(argv[1], "%llu%c", &tmp, &dummy) != 1 || tmp != (sector_t)tmp) { 317 ti->error = "Invalid device offset sector"; 318 return -EINVAL; 319 } 320 321 dd = kzalloc(sizeof(struct dust_device), GFP_KERNEL); 322 if (dd == NULL) { 323 ti->error = "Cannot allocate context"; 324 return -ENOMEM; 325 } 326 327 if (dm_get_device(ti, argv[0], dm_table_get_mode(ti->table), &dd->dev)) { 328 ti->error = "Device lookup failed"; 329 kfree(dd); 330 return -EINVAL; 331 } 332 333 dd->sect_per_block = sect_per_block; 334 dd->blksz = blksz; 335 dd->start = tmp; 336 337 dd->sect_per_block_shift = __ffs(sect_per_block); 338 339 /* 340 * Whether to fail a read on a "bad" block. 341 * Defaults to false; enabled later by message. 342 */ 343 dd->fail_read_on_bb = false; 344 345 /* 346 * Initialize bad block list rbtree. 347 */ 348 dd->badblocklist = RB_ROOT; 349 dd->badblock_count = 0; 350 spin_lock_init(&dd->dust_lock); 351 352 dd->quiet_mode = false; 353 354 BUG_ON(dm_set_target_max_io_len(ti, dd->sect_per_block) != 0); 355 356 ti->num_discard_bios = 1; 357 ti->num_flush_bios = 1; 358 ti->private = dd; 359 360 return 0; 361 } 362 363 static void dust_dtr(struct dm_target *ti) 364 { 365 struct dust_device *dd = ti->private; 366 367 __dust_clear_badblocks(&dd->badblocklist, dd->badblock_count); 368 dm_put_device(ti, dd->dev); 369 kfree(dd); 370 } 371 372 static int dust_message(struct dm_target *ti, unsigned int argc, char **argv, 373 char *result_buf, unsigned int maxlen) 374 { 375 struct dust_device *dd = ti->private; 376 sector_t size = i_size_read(dd->dev->bdev->bd_inode) >> SECTOR_SHIFT; 377 bool invalid_msg = false; 378 int result = -EINVAL; 379 unsigned long long tmp, block; 380 unsigned long flags; 381 char dummy; 382 383 if (argc == 1) { 384 if (!strcasecmp(argv[0], "addbadblock") || 385 !strcasecmp(argv[0], "removebadblock") || 386 !strcasecmp(argv[0], "queryblock")) { 387 DMERR("%s requires an additional argument", argv[0]); 388 } else if (!strcasecmp(argv[0], "disable")) { 389 DMINFO("disabling read failures on bad sectors"); 390 dd->fail_read_on_bb = false; 391 result = 0; 392 } else if (!strcasecmp(argv[0], "enable")) { 393 DMINFO("enabling read failures on bad sectors"); 394 dd->fail_read_on_bb = true; 395 result = 0; 396 } else if (!strcasecmp(argv[0], "countbadblocks")) { 397 spin_lock_irqsave(&dd->dust_lock, flags); 398 DMINFO("countbadblocks: %llu badblock(s) found", 399 dd->badblock_count); 400 spin_unlock_irqrestore(&dd->dust_lock, flags); 401 result = 0; 402 } else if (!strcasecmp(argv[0], "clearbadblocks")) { 403 result = dust_clear_badblocks(dd); 404 } else if (!strcasecmp(argv[0], "quiet")) { 405 if (!dd->quiet_mode) 406 dd->quiet_mode = true; 407 else 408 dd->quiet_mode = false; 409 result = 0; 410 } else { 411 invalid_msg = true; 412 } 413 } else if (argc == 2) { 414 if (sscanf(argv[1], "%llu%c", &tmp, &dummy) != 1) 415 return result; 416 417 block = tmp; 418 sector_div(size, dd->sect_per_block); 419 if (block > size) { 420 DMERR("selected block value out of range"); 421 return result; 422 } 423 424 if (!strcasecmp(argv[0], "addbadblock")) 425 result = dust_add_block(dd, block); 426 else if (!strcasecmp(argv[0], "removebadblock")) 427 result = dust_remove_block(dd, block); 428 else if (!strcasecmp(argv[0], "queryblock")) 429 result = dust_query_block(dd, block); 430 else 431 invalid_msg = true; 432 433 } else 434 DMERR("invalid number of arguments '%d'", argc); 435 436 if (invalid_msg) 437 DMERR("unrecognized message '%s' received", argv[0]); 438 439 return result; 440 } 441 442 static void dust_status(struct dm_target *ti, status_type_t type, 443 unsigned int status_flags, char *result, unsigned int maxlen) 444 { 445 struct dust_device *dd = ti->private; 446 unsigned int sz = 0; 447 448 switch (type) { 449 case STATUSTYPE_INFO: 450 DMEMIT("%s %s %s", dd->dev->name, 451 dd->fail_read_on_bb ? "fail_read_on_bad_block" : "bypass", 452 dd->quiet_mode ? "quiet" : "verbose"); 453 break; 454 455 case STATUSTYPE_TABLE: 456 DMEMIT("%s %llu %u", dd->dev->name, 457 (unsigned long long)dd->start, dd->blksz); 458 break; 459 } 460 } 461 462 static int dust_prepare_ioctl(struct dm_target *ti, struct block_device **bdev) 463 { 464 struct dust_device *dd = ti->private; 465 struct dm_dev *dev = dd->dev; 466 467 *bdev = dev->bdev; 468 469 /* 470 * Only pass ioctls through if the device sizes match exactly. 471 */ 472 if (dd->start || 473 ti->len != i_size_read(dev->bdev->bd_inode) >> SECTOR_SHIFT) 474 return 1; 475 476 return 0; 477 } 478 479 static int dust_iterate_devices(struct dm_target *ti, iterate_devices_callout_fn fn, 480 void *data) 481 { 482 struct dust_device *dd = ti->private; 483 484 return fn(ti, dd->dev, dd->start, ti->len, data); 485 } 486 487 static struct target_type dust_target = { 488 .name = "dust", 489 .version = {1, 0, 0}, 490 .module = THIS_MODULE, 491 .ctr = dust_ctr, 492 .dtr = dust_dtr, 493 .iterate_devices = dust_iterate_devices, 494 .map = dust_map, 495 .message = dust_message, 496 .status = dust_status, 497 .prepare_ioctl = dust_prepare_ioctl, 498 }; 499 500 static int __init dm_dust_init(void) 501 { 502 int result = dm_register_target(&dust_target); 503 504 if (result < 0) 505 DMERR("dm_register_target failed %d", result); 506 507 return result; 508 } 509 510 static void __exit dm_dust_exit(void) 511 { 512 dm_unregister_target(&dust_target); 513 } 514 515 module_init(dm_dust_init); 516 module_exit(dm_dust_exit); 517 518 MODULE_DESCRIPTION(DM_NAME " dust test target"); 519 MODULE_AUTHOR("Bryan Gurney <dm-devel@redhat.com>"); 520 MODULE_LICENSE("GPL"); 521