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