Linux-libre 5.3.12-gnu
[librecmc/linux-libre.git] / drivers / md / dm-dust.c
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");