1 // SPDX-License-Identifier: GPL-2.0
3 * Copyright (c) 2018 Red Hat, Inc.
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.
11 #include <linux/device-mapper.h>
12 #include <linux/module.h>
13 #include <linux/rbtree.h>
15 #define DM_MSG_PREFIX "dust"
20 unsigned char wr_fail_cnt
;
25 struct rb_root badblocklist
;
26 unsigned long long badblock_count
;
29 int sect_per_block_shift
;
30 unsigned int sect_per_block
;
32 bool fail_read_on_bb
:1;
36 static struct badblock
*dust_rb_search(struct rb_root
*root
, sector_t blk
)
38 struct rb_node
*node
= root
->rb_node
;
41 struct badblock
*bblk
= rb_entry(node
, struct badblock
, node
);
45 else if (bblk
->bb
< blk
)
46 node
= node
->rb_right
;
54 static bool dust_rb_insert(struct rb_root
*root
, struct badblock
*new)
56 struct badblock
*bblk
;
57 struct rb_node
**link
= &root
->rb_node
, *parent
= NULL
;
58 sector_t value
= new->bb
;
62 bblk
= rb_entry(parent
, struct badblock
, node
);
65 link
= &(*link
)->rb_left
;
66 else if (bblk
->bb
< value
)
67 link
= &(*link
)->rb_right
;
72 rb_link_node(&new->node
, parent
, link
);
73 rb_insert_color(&new->node
, root
);
78 static int dust_remove_block(struct dust_device
*dd
, unsigned long long block
)
80 struct badblock
*bblock
;
83 spin_lock_irqsave(&dd
->dust_lock
, flags
);
84 bblock
= dust_rb_search(&dd
->badblocklist
, block
);
87 if (!dd
->quiet_mode
) {
88 DMERR("%s: block %llu not found in badblocklist",
91 spin_unlock_irqrestore(&dd
->dust_lock
, flags
);
95 rb_erase(&bblock
->node
, &dd
->badblocklist
);
98 DMINFO("%s: badblock removed at block %llu", __func__
, block
);
100 spin_unlock_irqrestore(&dd
->dust_lock
, flags
);
105 static int dust_add_block(struct dust_device
*dd
, unsigned long long block
,
106 unsigned char wr_fail_cnt
)
108 struct badblock
*bblock
;
111 bblock
= kmalloc(sizeof(*bblock
), GFP_KERNEL
);
112 if (bblock
== NULL
) {
114 DMERR("%s: badblock allocation failed", __func__
);
118 spin_lock_irqsave(&dd
->dust_lock
, flags
);
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",
126 spin_unlock_irqrestore(&dd
->dust_lock
, flags
);
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
);
136 spin_unlock_irqrestore(&dd
->dust_lock
, flags
);
141 static int dust_query_block(struct dust_device
*dd
, unsigned long long block
, char *result
,
142 unsigned int maxlen
, unsigned int *sz_ptr
)
144 struct badblock
*bblock
;
146 unsigned int sz
= *sz_ptr
;
148 spin_lock_irqsave(&dd
->dust_lock
, flags
);
149 bblock
= dust_rb_search(&dd
->badblocklist
, block
);
151 DMEMIT("%s: block %llu found in badblocklist", __func__
, block
);
153 DMEMIT("%s: block %llu not found in badblocklist", __func__
, block
);
154 spin_unlock_irqrestore(&dd
->dust_lock
, flags
);
159 static int __dust_map_read(struct dust_device
*dd
, sector_t thisblock
)
161 struct badblock
*bblk
= dust_rb_search(&dd
->badblocklist
, thisblock
);
164 return DM_MAPIO_KILL
;
166 return DM_MAPIO_REMAPPED
;
169 static int dust_map_read(struct dust_device
*dd
, sector_t thisblock
,
170 bool fail_read_on_bb
)
173 int r
= DM_MAPIO_REMAPPED
;
175 if (fail_read_on_bb
) {
176 thisblock
>>= dd
->sect_per_block_shift
;
177 spin_lock_irqsave(&dd
->dust_lock
, flags
);
178 r
= __dust_map_read(dd
, thisblock
);
179 spin_unlock_irqrestore(&dd
->dust_lock
, flags
);
185 static int __dust_map_write(struct dust_device
*dd
, sector_t thisblock
)
187 struct badblock
*bblk
= dust_rb_search(&dd
->badblocklist
, thisblock
);
189 if (bblk
&& bblk
->wr_fail_cnt
> 0) {
191 return DM_MAPIO_KILL
;
195 rb_erase(&bblk
->node
, &dd
->badblocklist
);
196 dd
->badblock_count
--;
198 if (!dd
->quiet_mode
) {
199 sector_div(thisblock
, dd
->sect_per_block
);
200 DMINFO("block %llu removed from badblocklist by write",
201 (unsigned long long)thisblock
);
205 return DM_MAPIO_REMAPPED
;
208 static int dust_map_write(struct dust_device
*dd
, sector_t thisblock
,
209 bool fail_read_on_bb
)
212 int r
= DM_MAPIO_REMAPPED
;
214 if (fail_read_on_bb
) {
215 thisblock
>>= dd
->sect_per_block_shift
;
216 spin_lock_irqsave(&dd
->dust_lock
, flags
);
217 r
= __dust_map_write(dd
, thisblock
);
218 spin_unlock_irqrestore(&dd
->dust_lock
, flags
);
224 static int dust_map(struct dm_target
*ti
, struct bio
*bio
)
226 struct dust_device
*dd
= ti
->private;
229 bio_set_dev(bio
, dd
->dev
->bdev
);
230 bio
->bi_iter
.bi_sector
= dd
->start
+ dm_target_offset(ti
, bio
->bi_iter
.bi_sector
);
232 if (bio_data_dir(bio
) == READ
)
233 r
= dust_map_read(dd
, bio
->bi_iter
.bi_sector
, dd
->fail_read_on_bb
);
235 r
= dust_map_write(dd
, bio
->bi_iter
.bi_sector
, dd
->fail_read_on_bb
);
240 static bool __dust_clear_badblocks(struct rb_root
*tree
,
241 unsigned long long count
)
243 struct rb_node
*node
= NULL
, *nnode
= NULL
;
245 nnode
= rb_first(tree
);
253 nnode
= rb_next(node
);
254 rb_erase(node
, tree
);
259 BUG_ON(tree
->rb_node
!= NULL
);
264 static int dust_clear_badblocks(struct dust_device
*dd
, char *result
, unsigned int maxlen
,
265 unsigned int *sz_ptr
)
268 struct rb_root badblocklist
;
269 unsigned long long badblock_count
;
270 unsigned int sz
= *sz_ptr
;
272 spin_lock_irqsave(&dd
->dust_lock
, flags
);
273 badblocklist
= dd
->badblocklist
;
274 badblock_count
= dd
->badblock_count
;
275 dd
->badblocklist
= RB_ROOT
;
276 dd
->badblock_count
= 0;
277 spin_unlock_irqrestore(&dd
->dust_lock
, flags
);
279 if (!__dust_clear_badblocks(&badblocklist
, badblock_count
))
280 DMEMIT("%s: no badblocks found", __func__
);
282 DMEMIT("%s: badblocks cleared", __func__
);
287 static int dust_list_badblocks(struct dust_device
*dd
, char *result
, unsigned int maxlen
,
288 unsigned int *sz_ptr
)
291 struct rb_root badblocklist
;
292 struct rb_node
*node
;
293 struct badblock
*bblk
;
294 unsigned int sz
= *sz_ptr
;
295 unsigned long long num
= 0;
297 spin_lock_irqsave(&dd
->dust_lock
, flags
);
298 badblocklist
= dd
->badblocklist
;
299 for (node
= rb_first(&badblocklist
); node
; node
= rb_next(node
)) {
300 bblk
= rb_entry(node
, struct badblock
, node
);
301 DMEMIT("%llu\n", bblk
->bb
);
305 spin_unlock_irqrestore(&dd
->dust_lock
, flags
);
307 DMEMIT("No blocks in badblocklist");
315 * <device_path> <offset> <blksz>
317 * device_path: path to the block device
318 * offset: offset to data area from start of device_path
319 * blksz: block size (minimum 512, maximum 1073741824, must be a power of 2)
321 static int dust_ctr(struct dm_target
*ti
, unsigned int argc
, char **argv
)
323 struct dust_device
*dd
;
324 unsigned long long tmp
;
327 unsigned int sect_per_block
;
328 sector_t DUST_MAX_BLKSZ_SECTORS
= 2097152;
329 sector_t max_block_sectors
= min(ti
->len
, DUST_MAX_BLKSZ_SECTORS
);
332 ti
->error
= "Invalid argument count";
336 if (kstrtouint(argv
[2], 10, &blksz
) || !blksz
) {
337 ti
->error
= "Invalid block size parameter";
342 ti
->error
= "Block size must be at least 512";
346 if (!is_power_of_2(blksz
)) {
347 ti
->error
= "Block size must be a power of 2";
351 if (to_sector(blksz
) > max_block_sectors
) {
352 ti
->error
= "Block size is too large";
356 sect_per_block
= (blksz
>> SECTOR_SHIFT
);
358 if (sscanf(argv
[1], "%llu%c", &tmp
, &dummy
) != 1 || tmp
!= (sector_t
)tmp
) {
359 ti
->error
= "Invalid device offset sector";
363 dd
= kzalloc(sizeof(struct dust_device
), GFP_KERNEL
);
365 ti
->error
= "Cannot allocate context";
369 if (dm_get_device(ti
, argv
[0], dm_table_get_mode(ti
->table
), &dd
->dev
)) {
370 ti
->error
= "Device lookup failed";
375 dd
->sect_per_block
= sect_per_block
;
379 dd
->sect_per_block_shift
= __ffs(sect_per_block
);
382 * Whether to fail a read on a "bad" block.
383 * Defaults to false; enabled later by message.
385 dd
->fail_read_on_bb
= false;
388 * Initialize bad block list rbtree.
390 dd
->badblocklist
= RB_ROOT
;
391 dd
->badblock_count
= 0;
392 spin_lock_init(&dd
->dust_lock
);
394 dd
->quiet_mode
= false;
396 BUG_ON(dm_set_target_max_io_len(ti
, dd
->sect_per_block
) != 0);
398 ti
->num_discard_bios
= 1;
399 ti
->num_flush_bios
= 1;
405 static void dust_dtr(struct dm_target
*ti
)
407 struct dust_device
*dd
= ti
->private;
409 __dust_clear_badblocks(&dd
->badblocklist
, dd
->badblock_count
);
410 dm_put_device(ti
, dd
->dev
);
414 static int dust_message(struct dm_target
*ti
, unsigned int argc
, char **argv
,
415 char *result
, unsigned int maxlen
)
417 struct dust_device
*dd
= ti
->private;
418 sector_t size
= i_size_read(dd
->dev
->bdev
->bd_inode
) >> SECTOR_SHIFT
;
419 bool invalid_msg
= false;
421 unsigned long long tmp
, block
;
422 unsigned char wr_fail_cnt
;
429 if (!strcasecmp(argv
[0], "addbadblock") ||
430 !strcasecmp(argv
[0], "removebadblock") ||
431 !strcasecmp(argv
[0], "queryblock")) {
432 DMERR("%s requires an additional argument", argv
[0]);
433 } else if (!strcasecmp(argv
[0], "disable")) {
434 DMINFO("disabling read failures on bad sectors");
435 dd
->fail_read_on_bb
= false;
437 } else if (!strcasecmp(argv
[0], "enable")) {
438 DMINFO("enabling read failures on bad sectors");
439 dd
->fail_read_on_bb
= true;
441 } else if (!strcasecmp(argv
[0], "countbadblocks")) {
442 spin_lock_irqsave(&dd
->dust_lock
, flags
);
443 DMEMIT("countbadblocks: %llu badblock(s) found",
445 spin_unlock_irqrestore(&dd
->dust_lock
, flags
);
447 } else if (!strcasecmp(argv
[0], "clearbadblocks")) {
448 r
= dust_clear_badblocks(dd
, result
, maxlen
, &sz
);
449 } else if (!strcasecmp(argv
[0], "quiet")) {
451 dd
->quiet_mode
= true;
453 dd
->quiet_mode
= false;
455 } else if (!strcasecmp(argv
[0], "listbadblocks")) {
456 r
= dust_list_badblocks(dd
, result
, maxlen
, &sz
);
460 } else if (argc
== 2) {
461 if (sscanf(argv
[1], "%llu%c", &tmp
, &dummy
) != 1)
465 sector_div(size
, dd
->sect_per_block
);
467 DMERR("selected block value out of range");
471 if (!strcasecmp(argv
[0], "addbadblock"))
472 r
= dust_add_block(dd
, block
, 0);
473 else if (!strcasecmp(argv
[0], "removebadblock"))
474 r
= dust_remove_block(dd
, block
);
475 else if (!strcasecmp(argv
[0], "queryblock"))
476 r
= dust_query_block(dd
, block
, result
, maxlen
, &sz
);
480 } else if (argc
== 3) {
481 if (sscanf(argv
[1], "%llu%c", &tmp
, &dummy
) != 1)
484 if (sscanf(argv
[2], "%u%c", &tmp_ui
, &dummy
) != 1)
489 DMERR("selected write fail count out of range");
492 wr_fail_cnt
= tmp_ui
;
493 sector_div(size
, dd
->sect_per_block
);
495 DMERR("selected block value out of range");
499 if (!strcasecmp(argv
[0], "addbadblock"))
500 r
= dust_add_block(dd
, block
, wr_fail_cnt
);
505 DMERR("invalid number of arguments '%d'", argc
);
508 DMERR("unrecognized message '%s' received", argv
[0]);
513 static void dust_status(struct dm_target
*ti
, status_type_t type
,
514 unsigned int status_flags
, char *result
, unsigned int maxlen
)
516 struct dust_device
*dd
= ti
->private;
520 case STATUSTYPE_INFO
:
521 DMEMIT("%s %s %s", dd
->dev
->name
,
522 dd
->fail_read_on_bb
? "fail_read_on_bad_block" : "bypass",
523 dd
->quiet_mode
? "quiet" : "verbose");
526 case STATUSTYPE_TABLE
:
527 DMEMIT("%s %llu %u", dd
->dev
->name
,
528 (unsigned long long)dd
->start
, dd
->blksz
);
533 static int dust_prepare_ioctl(struct dm_target
*ti
, struct block_device
**bdev
)
535 struct dust_device
*dd
= ti
->private;
536 struct dm_dev
*dev
= dd
->dev
;
541 * Only pass ioctls through if the device sizes match exactly.
544 ti
->len
!= i_size_read(dev
->bdev
->bd_inode
) >> SECTOR_SHIFT
)
550 static int dust_iterate_devices(struct dm_target
*ti
, iterate_devices_callout_fn fn
,
553 struct dust_device
*dd
= ti
->private;
555 return fn(ti
, dd
->dev
, dd
->start
, ti
->len
, data
);
558 static struct target_type dust_target
= {
560 .version
= {1, 0, 0},
561 .module
= THIS_MODULE
,
564 .iterate_devices
= dust_iterate_devices
,
566 .message
= dust_message
,
567 .status
= dust_status
,
568 .prepare_ioctl
= dust_prepare_ioctl
,
571 static int __init
dm_dust_init(void)
573 int r
= dm_register_target(&dust_target
);
576 DMERR("dm_register_target failed %d", r
);
581 static void __exit
dm_dust_exit(void)
583 dm_unregister_target(&dust_target
);
586 module_init(dm_dust_init
);
587 module_exit(dm_dust_exit
);
589 MODULE_DESCRIPTION(DM_NAME
" dust test target");
590 MODULE_AUTHOR("Bryan Gurney <dm-devel@redhat.com>");
591 MODULE_LICENSE("GPL");