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
dust_rb_search(struct rb_root * root,sector_t blk)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
dust_rb_insert(struct rb_root * root,struct badblock * new)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
dust_remove_block(struct dust_device * dd,unsigned long long block)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
dust_add_block(struct dust_device * dd,unsigned long long block)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
dust_query_block(struct dust_device * dd,unsigned long long block)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
__dust_map_read(struct dust_device * dd,sector_t thisblock)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
dust_map_read(struct dust_device * dd,sector_t thisblock,bool fail_read_on_bb)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
__dust_map_write(struct dust_device * dd,sector_t thisblock)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
dust_map_write(struct dust_device * dd,sector_t thisblock,bool fail_read_on_bb)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
dust_map(struct dm_target * ti,struct bio * bio)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
__dust_clear_badblocks(struct rb_root * tree,unsigned long long count)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
dust_clear_badblocks(struct dust_device * dd)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 */
dust_ctr(struct dm_target * ti,unsigned int argc,char ** argv)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
dust_dtr(struct dm_target * ti)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
dust_message(struct dm_target * ti,unsigned int argc,char ** argv,char * result_buf,unsigned int maxlen)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
dust_status(struct dm_target * ti,status_type_t type,unsigned int status_flags,char * result,unsigned int maxlen)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
dust_prepare_ioctl(struct dm_target * ti,struct block_device ** bdev)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
dust_iterate_devices(struct dm_target * ti,iterate_devices_callout_fn fn,void * data)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
dm_dust_init(void)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
dm_dust_exit(void)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