xref: /linux/drivers/md/dm-dust.c (revision 17cfcb68af3bc7d5e8ae08779b1853310a2949f3)
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