xref: /linux/block/error-injection.c (revision 214cdae69dba9bb1fc0b517b7fb97bab385a2e3a)
1e8dcf2d1SChristoph Hellwig // SPDX-License-Identifier: GPL-2.0
2e8dcf2d1SChristoph Hellwig /*
3e8dcf2d1SChristoph Hellwig  * Copyright (c) 2026 Christoph Hellwig.
4e8dcf2d1SChristoph Hellwig  */
5e8dcf2d1SChristoph Hellwig #include <linux/debugfs.h>
6e8dcf2d1SChristoph Hellwig #include <linux/blkdev.h>
7e8dcf2d1SChristoph Hellwig #include <linux/parser.h>
8e8dcf2d1SChristoph Hellwig #include <linux/seq_file.h>
9e8dcf2d1SChristoph Hellwig #include "blk.h"
10e8dcf2d1SChristoph Hellwig #include "error-injection.h"
11e8dcf2d1SChristoph Hellwig 
12e8dcf2d1SChristoph Hellwig struct blk_error_inject {
13e8dcf2d1SChristoph Hellwig 	struct list_head		entry;
14e8dcf2d1SChristoph Hellwig 	sector_t			start;
15e8dcf2d1SChristoph Hellwig 	sector_t			end;
16e8dcf2d1SChristoph Hellwig 	enum req_op			op;
17e8dcf2d1SChristoph Hellwig 	blk_status_t			status;
18e8dcf2d1SChristoph Hellwig 
19e8dcf2d1SChristoph Hellwig 	/* only inject every 1 / chance times */
20e8dcf2d1SChristoph Hellwig 	unsigned int			chance;
21e8dcf2d1SChristoph Hellwig };
22e8dcf2d1SChristoph Hellwig 
23e8dcf2d1SChristoph Hellwig DEFINE_STATIC_KEY_FALSE(blk_error_injection_enabled);
24e8dcf2d1SChristoph Hellwig 
25e8dcf2d1SChristoph Hellwig bool __blk_error_inject(struct bio *bio)
26e8dcf2d1SChristoph Hellwig {
27e8dcf2d1SChristoph Hellwig 	struct gendisk *disk = bio->bi_bdev->bd_disk;
28e8dcf2d1SChristoph Hellwig 	struct blk_error_inject *inj;
29e8dcf2d1SChristoph Hellwig 
30e8dcf2d1SChristoph Hellwig 	rcu_read_lock();
31e8dcf2d1SChristoph Hellwig 	list_for_each_entry_rcu(inj, &disk->error_injection_list, entry) {
32e8dcf2d1SChristoph Hellwig 		if (bio_op(bio) != inj->op)
33e8dcf2d1SChristoph Hellwig 			continue;
34e8dcf2d1SChristoph Hellwig 		/*
35e8dcf2d1SChristoph Hellwig 		 * This never matches 0-sized bios like empty WRITEs with
36e8dcf2d1SChristoph Hellwig 		 * REQ_PREFLUSH or ZONE_RESET_ALL.  While adding a special case
37e8dcf2d1SChristoph Hellwig 		 * for them would be trivial, that means any WRITE rule would
38e8dcf2d1SChristoph Hellwig 		 * trigger for flushes.  So before we can make this work
39e8dcf2d1SChristoph Hellwig 		 * properly, we'll need to start using REQ_OP_FLUSH for pure
40e8dcf2d1SChristoph Hellwig 		 * flushes at the bio level like we already do in blk-mq.
41e8dcf2d1SChristoph Hellwig 		 */
42e8dcf2d1SChristoph Hellwig 		if (bio->bi_iter.bi_sector > inj->end ||
43e8dcf2d1SChristoph Hellwig 		    bio_end_sector(bio) <= inj->start)
44e8dcf2d1SChristoph Hellwig 			continue;
45e8dcf2d1SChristoph Hellwig 		if (inj->chance > 1 && (get_random_u32() % inj->chance) != 0)
46e8dcf2d1SChristoph Hellwig 			continue;
47e8dcf2d1SChristoph Hellwig 
48e8dcf2d1SChristoph Hellwig 		pr_info_ratelimited("%pg: injecting %s error for %s at sector %llu:%u\n",
49e8dcf2d1SChristoph Hellwig 				disk->part0, blk_status_to_str(inj->status),
50e8dcf2d1SChristoph Hellwig 				blk_op_str(inj->op), bio->bi_iter.bi_sector,
51e8dcf2d1SChristoph Hellwig 				bio_sectors(bio));
52e8dcf2d1SChristoph Hellwig 		bio->bi_status = inj->status;
53e8dcf2d1SChristoph Hellwig 		rcu_read_unlock();
54e8dcf2d1SChristoph Hellwig 		bio_endio(bio);
55e8dcf2d1SChristoph Hellwig 		return true;
56e8dcf2d1SChristoph Hellwig 	}
57e8dcf2d1SChristoph Hellwig 	rcu_read_unlock();
58e8dcf2d1SChristoph Hellwig 	return false;
59e8dcf2d1SChristoph Hellwig }
60e8dcf2d1SChristoph Hellwig 
61e8dcf2d1SChristoph Hellwig static int error_inject_add(struct gendisk *disk, enum req_op op,
62e8dcf2d1SChristoph Hellwig 		sector_t start, u64 nr_sectors, blk_status_t status,
63e8dcf2d1SChristoph Hellwig 		unsigned int chance)
64e8dcf2d1SChristoph Hellwig {
65e8dcf2d1SChristoph Hellwig 	struct blk_error_inject *inj;
66e8dcf2d1SChristoph Hellwig 	int error = -EINVAL;
67e8dcf2d1SChristoph Hellwig 
68e8dcf2d1SChristoph Hellwig 	if (op == REQ_OP_LAST)
69e8dcf2d1SChristoph Hellwig 		return -EINVAL;
70e8dcf2d1SChristoph Hellwig 	if (status == BLK_STS_OK)
71e8dcf2d1SChristoph Hellwig 		return -EINVAL;
72e8dcf2d1SChristoph Hellwig 
73e8dcf2d1SChristoph Hellwig 	inj = kzalloc_obj(*inj);
74e8dcf2d1SChristoph Hellwig 	if (!inj)
75e8dcf2d1SChristoph Hellwig 		return -ENOMEM;
76e8dcf2d1SChristoph Hellwig 
77e8dcf2d1SChristoph Hellwig 	if (nr_sectors) {
78e8dcf2d1SChristoph Hellwig 		if (U64_MAX - nr_sectors < start)
79e8dcf2d1SChristoph Hellwig 			goto out_free_inj;
80e8dcf2d1SChristoph Hellwig 		inj->end = start + nr_sectors - 1;
81e8dcf2d1SChristoph Hellwig 	} else {
82e8dcf2d1SChristoph Hellwig 		inj->end = U64_MAX;
83e8dcf2d1SChristoph Hellwig 	}
84e8dcf2d1SChristoph Hellwig 
85e8dcf2d1SChristoph Hellwig 	inj->op = op;
86e8dcf2d1SChristoph Hellwig 	inj->start = start;
87e8dcf2d1SChristoph Hellwig 	inj->status = status;
88e8dcf2d1SChristoph Hellwig 	inj->chance = chance;
89e8dcf2d1SChristoph Hellwig 
90e8dcf2d1SChristoph Hellwig 	pr_debug_ratelimited("%pg: adding %s injection for %s at sector %llu:%llu\n",
91e8dcf2d1SChristoph Hellwig 			disk->part0, blk_status_to_str(status),
92e8dcf2d1SChristoph Hellwig 			blk_op_str(op),
93e8dcf2d1SChristoph Hellwig 			start, nr_sectors);
94e8dcf2d1SChristoph Hellwig 
95e8dcf2d1SChristoph Hellwig 	/*
96e8dcf2d1SChristoph Hellwig 	 * Add to the front of the list so that newer entries can partially
97e8dcf2d1SChristoph Hellwig 	 * override other entries.  This also intentionally allows duplicate
98e8dcf2d1SChristoph Hellwig 	 * entries as there is no real reason to reject them.
99e8dcf2d1SChristoph Hellwig 	 */
100e8dcf2d1SChristoph Hellwig 	mutex_lock(&disk->error_injection_lock);
101e8dcf2d1SChristoph Hellwig 	if (!disk_live(disk)) {
102e8dcf2d1SChristoph Hellwig 		mutex_unlock(&disk->error_injection_lock);
103e8dcf2d1SChristoph Hellwig 		error = -ENODEV;
104e8dcf2d1SChristoph Hellwig 		goto out_free_inj;
105e8dcf2d1SChristoph Hellwig 	}
106e8dcf2d1SChristoph Hellwig 	if (list_empty(&disk->error_injection_list))
107e8dcf2d1SChristoph Hellwig 		static_branch_inc(&blk_error_injection_enabled);
108e8dcf2d1SChristoph Hellwig 	list_add_rcu(&inj->entry, &disk->error_injection_list);
109e8dcf2d1SChristoph Hellwig 	set_bit(GD_ERROR_INJECT, &disk->state);
110e8dcf2d1SChristoph Hellwig 	mutex_unlock(&disk->error_injection_lock);
111e8dcf2d1SChristoph Hellwig 	return 0;
112e8dcf2d1SChristoph Hellwig 
113e8dcf2d1SChristoph Hellwig out_free_inj:
114e8dcf2d1SChristoph Hellwig 	kfree(inj);
115e8dcf2d1SChristoph Hellwig 	return error;
116e8dcf2d1SChristoph Hellwig }
117e8dcf2d1SChristoph Hellwig 
118e8dcf2d1SChristoph Hellwig static void error_inject_removeall(struct gendisk *disk)
119e8dcf2d1SChristoph Hellwig {
120e8dcf2d1SChristoph Hellwig 	struct blk_error_inject *inj;
121e8dcf2d1SChristoph Hellwig 
122e8dcf2d1SChristoph Hellwig 	mutex_lock(&disk->error_injection_lock);
123*214cdae6SChristoph Hellwig 	if (test_and_clear_bit(GD_ERROR_INJECT, &disk->state))
124*214cdae6SChristoph Hellwig 		static_branch_dec(&blk_error_injection_enabled);
125e8dcf2d1SChristoph Hellwig 	while ((inj = list_first_entry_or_null(&disk->error_injection_list,
126e8dcf2d1SChristoph Hellwig 			struct blk_error_inject, entry))) {
127e8dcf2d1SChristoph Hellwig 		list_del_rcu(&inj->entry);
128e8dcf2d1SChristoph Hellwig 		kfree_rcu_mightsleep(inj);
129e8dcf2d1SChristoph Hellwig 	}
130e8dcf2d1SChristoph Hellwig 	mutex_unlock(&disk->error_injection_lock);
131e8dcf2d1SChristoph Hellwig }
132e8dcf2d1SChristoph Hellwig 
133e8dcf2d1SChristoph Hellwig enum options {
134e8dcf2d1SChristoph Hellwig 	Opt_add			= (1u << 0),
135e8dcf2d1SChristoph Hellwig 	Opt_removeall		= (1u << 1),
136e8dcf2d1SChristoph Hellwig 
137e8dcf2d1SChristoph Hellwig 	Opt_op			= (1u << 16),
138e8dcf2d1SChristoph Hellwig 	Opt_start		= (1u << 17),
139e8dcf2d1SChristoph Hellwig 	Opt_nr_sectors		= (1u << 18),
140e8dcf2d1SChristoph Hellwig 	Opt_status		= (1u << 19),
141e8dcf2d1SChristoph Hellwig 	Opt_chance		= (1u << 20),
142e8dcf2d1SChristoph Hellwig 
143e8dcf2d1SChristoph Hellwig 	Opt_invalid,
144e8dcf2d1SChristoph Hellwig };
145e8dcf2d1SChristoph Hellwig 
146e8dcf2d1SChristoph Hellwig static const match_table_t opt_tokens = {
147e8dcf2d1SChristoph Hellwig 	{ Opt_add,			"add",			},
148e8dcf2d1SChristoph Hellwig 	{ Opt_removeall,		"removeall",		},
149e8dcf2d1SChristoph Hellwig 	{ Opt_op,			"op=%s",		},
150e8dcf2d1SChristoph Hellwig 	{ Opt_start,			"start=%u"		},
151e8dcf2d1SChristoph Hellwig 	{ Opt_nr_sectors,		"nr_sectors=%u"		},
152e8dcf2d1SChristoph Hellwig 	{ Opt_status,			"status=%s"		},
153e8dcf2d1SChristoph Hellwig 	{ Opt_chance,			"chance=%u"		},
154e8dcf2d1SChristoph Hellwig 	{ Opt_invalid,			NULL,			},
155e8dcf2d1SChristoph Hellwig };
156e8dcf2d1SChristoph Hellwig 
157e8dcf2d1SChristoph Hellwig static int match_op(substring_t *args, enum req_op *op)
158e8dcf2d1SChristoph Hellwig {
159e8dcf2d1SChristoph Hellwig 	const char *tag;
160e8dcf2d1SChristoph Hellwig 
161e8dcf2d1SChristoph Hellwig 	tag = match_strdup(args);
162e8dcf2d1SChristoph Hellwig 	if (!tag)
163e8dcf2d1SChristoph Hellwig 		return -ENOMEM;
164e8dcf2d1SChristoph Hellwig 	*op = str_to_blk_op(tag);
165e8dcf2d1SChristoph Hellwig 	if (*op == REQ_OP_LAST)
166e8dcf2d1SChristoph Hellwig 		pr_warn("invalid op '%s'\n", tag);
167e8dcf2d1SChristoph Hellwig 	kfree(tag);
168e8dcf2d1SChristoph Hellwig 	return 0;
169e8dcf2d1SChristoph Hellwig }
170e8dcf2d1SChristoph Hellwig 
171e8dcf2d1SChristoph Hellwig static int match_status(substring_t *args, blk_status_t *status)
172e8dcf2d1SChristoph Hellwig {
173e8dcf2d1SChristoph Hellwig 	const char *tag;
174e8dcf2d1SChristoph Hellwig 
175e8dcf2d1SChristoph Hellwig 	tag = match_strdup(args);
176e8dcf2d1SChristoph Hellwig 	if (!tag)
177e8dcf2d1SChristoph Hellwig 		return -ENOMEM;
178e8dcf2d1SChristoph Hellwig 	*status = tag_to_blk_status(tag);
179e8dcf2d1SChristoph Hellwig 	if (!*status)
180e8dcf2d1SChristoph Hellwig 		pr_warn("invalid status '%s'\n", tag);
181e8dcf2d1SChristoph Hellwig 	kfree(tag);
182e8dcf2d1SChristoph Hellwig 	return 0;
183e8dcf2d1SChristoph Hellwig }
184e8dcf2d1SChristoph Hellwig 
185e8dcf2d1SChristoph Hellwig static ssize_t blk_error_injection_parse_options(struct gendisk *disk,
186e8dcf2d1SChristoph Hellwig 		char *options)
187e8dcf2d1SChristoph Hellwig {
188e8dcf2d1SChristoph Hellwig 	enum { Unset, Add, Removeall } action = Unset;
189e8dcf2d1SChristoph Hellwig 	unsigned int option_mask = 0, chance = 1;
190e8dcf2d1SChristoph Hellwig 	enum req_op op = REQ_OP_LAST;
191e8dcf2d1SChristoph Hellwig 	u64 start = 0, nr_sectors = 0;
192e8dcf2d1SChristoph Hellwig 	blk_status_t status = BLK_STS_OK;
193e8dcf2d1SChristoph Hellwig 	substring_t args[MAX_OPT_ARGS];
194e8dcf2d1SChristoph Hellwig 	char *p;
195e8dcf2d1SChristoph Hellwig 
196e8dcf2d1SChristoph Hellwig 	while ((p = strsep(&options, ",\n")) != NULL) {
197e8dcf2d1SChristoph Hellwig 		int error = 0;
198e8dcf2d1SChristoph Hellwig 		ssize_t token;
199e8dcf2d1SChristoph Hellwig 
200e8dcf2d1SChristoph Hellwig 		if (!*p)
201e8dcf2d1SChristoph Hellwig 			continue;
202e8dcf2d1SChristoph Hellwig 		token = match_token(p, opt_tokens, args);
203e8dcf2d1SChristoph Hellwig 		option_mask |= token;
204e8dcf2d1SChristoph Hellwig 		switch (token) {
205e8dcf2d1SChristoph Hellwig 		case Opt_add:
206e8dcf2d1SChristoph Hellwig 			if (action != Unset)
207e8dcf2d1SChristoph Hellwig 				return -EINVAL;
208e8dcf2d1SChristoph Hellwig 			action = Add;
209e8dcf2d1SChristoph Hellwig 			break;
210e8dcf2d1SChristoph Hellwig 		case Opt_removeall:
211e8dcf2d1SChristoph Hellwig 			if (action != Unset)
212e8dcf2d1SChristoph Hellwig 				return -EINVAL;
213e8dcf2d1SChristoph Hellwig 			action = Removeall;
214e8dcf2d1SChristoph Hellwig 			break;
215e8dcf2d1SChristoph Hellwig 		case Opt_op:
216e8dcf2d1SChristoph Hellwig 			error = match_op(args, &op);
217e8dcf2d1SChristoph Hellwig 			break;
218e8dcf2d1SChristoph Hellwig 		case Opt_start:
219e8dcf2d1SChristoph Hellwig 			error = match_u64(args, &start);
220e8dcf2d1SChristoph Hellwig 			break;
221e8dcf2d1SChristoph Hellwig 		case Opt_nr_sectors:
222e8dcf2d1SChristoph Hellwig 			error = match_u64(args, &nr_sectors);
223e8dcf2d1SChristoph Hellwig 			break;
224e8dcf2d1SChristoph Hellwig 		case Opt_status:
225e8dcf2d1SChristoph Hellwig 			error = match_status(args, &status);
226e8dcf2d1SChristoph Hellwig 			break;
227e8dcf2d1SChristoph Hellwig 		case Opt_chance:
228e8dcf2d1SChristoph Hellwig 			error = match_uint(args, &chance);
229e8dcf2d1SChristoph Hellwig 			if (!error && chance == 0)
230e8dcf2d1SChristoph Hellwig 				error = -EINVAL;
231e8dcf2d1SChristoph Hellwig 			break;
232e8dcf2d1SChristoph Hellwig 		default:
233e8dcf2d1SChristoph Hellwig 			pr_warn("unknown parameter or missing value '%s'\n", p);
234e8dcf2d1SChristoph Hellwig 			error = -EINVAL;
235e8dcf2d1SChristoph Hellwig 		}
236e8dcf2d1SChristoph Hellwig 		if (error)
237e8dcf2d1SChristoph Hellwig 			return error;
238e8dcf2d1SChristoph Hellwig 	}
239e8dcf2d1SChristoph Hellwig 
240e8dcf2d1SChristoph Hellwig 	switch (action) {
241e8dcf2d1SChristoph Hellwig 	case Add:
242e8dcf2d1SChristoph Hellwig 		return error_inject_add(disk, op, start, nr_sectors, status,
243e8dcf2d1SChristoph Hellwig 				chance);
244e8dcf2d1SChristoph Hellwig 	case Removeall:
245e8dcf2d1SChristoph Hellwig 		if (option_mask & ~Opt_removeall)
246e8dcf2d1SChristoph Hellwig 			return -EINVAL;
247e8dcf2d1SChristoph Hellwig 		error_inject_removeall(disk);
248e8dcf2d1SChristoph Hellwig 		return 0;
249e8dcf2d1SChristoph Hellwig 	default:
250e8dcf2d1SChristoph Hellwig 		return -EINVAL;
251e8dcf2d1SChristoph Hellwig 	}
252e8dcf2d1SChristoph Hellwig }
253e8dcf2d1SChristoph Hellwig 
254e8dcf2d1SChristoph Hellwig static ssize_t blk_error_injection_write(struct file *file,
255e8dcf2d1SChristoph Hellwig 		const char __user *ubuf, size_t count, loff_t *pos)
256e8dcf2d1SChristoph Hellwig {
257e8dcf2d1SChristoph Hellwig 	struct gendisk *disk = file_inode(file)->i_private;
258e8dcf2d1SChristoph Hellwig 	char *options;
259e8dcf2d1SChristoph Hellwig 	int error;
260e8dcf2d1SChristoph Hellwig 
261e8dcf2d1SChristoph Hellwig 	options = memdup_user_nul(ubuf, count);
262e8dcf2d1SChristoph Hellwig 	if (IS_ERR(options))
263e8dcf2d1SChristoph Hellwig 		return PTR_ERR(options);
264e8dcf2d1SChristoph Hellwig 	error = blk_error_injection_parse_options(disk, options);
265e8dcf2d1SChristoph Hellwig 	kfree(options);
266e8dcf2d1SChristoph Hellwig 
267e8dcf2d1SChristoph Hellwig 	if (error)
268e8dcf2d1SChristoph Hellwig 		return error;
269e8dcf2d1SChristoph Hellwig 	return count;
270e8dcf2d1SChristoph Hellwig }
271e8dcf2d1SChristoph Hellwig 
272e8dcf2d1SChristoph Hellwig static int blk_error_injection_show(struct seq_file *s, void *private)
273e8dcf2d1SChristoph Hellwig {
274e8dcf2d1SChristoph Hellwig 	struct gendisk *disk = s->private;
275e8dcf2d1SChristoph Hellwig 	struct blk_error_inject *inj;
276e8dcf2d1SChristoph Hellwig 
277e8dcf2d1SChristoph Hellwig 	rcu_read_lock();
278e8dcf2d1SChristoph Hellwig 	list_for_each_entry_rcu(inj, &disk->error_injection_list, entry) {
279e8dcf2d1SChristoph Hellwig 		seq_printf(s, "%llu:%llu status=%s,chance=%u",
280e8dcf2d1SChristoph Hellwig 			inj->start, inj->end,
281e8dcf2d1SChristoph Hellwig 			blk_status_to_tag(inj->status), inj->chance);
282e8dcf2d1SChristoph Hellwig 		seq_putc(s, '\n');
283e8dcf2d1SChristoph Hellwig 	}
284e8dcf2d1SChristoph Hellwig 	rcu_read_unlock();
285e8dcf2d1SChristoph Hellwig 	return 0;
286e8dcf2d1SChristoph Hellwig }
287e8dcf2d1SChristoph Hellwig 
288e8dcf2d1SChristoph Hellwig static int blk_error_injection_open(struct inode *inode, struct file *file)
289e8dcf2d1SChristoph Hellwig {
290e8dcf2d1SChristoph Hellwig 	return single_open(file, blk_error_injection_show, inode->i_private);
291e8dcf2d1SChristoph Hellwig }
292e8dcf2d1SChristoph Hellwig 
293e8dcf2d1SChristoph Hellwig static int blk_error_injection_release(struct inode *inode, struct file *file)
294e8dcf2d1SChristoph Hellwig {
295e8dcf2d1SChristoph Hellwig 	return single_release(inode, file);
296e8dcf2d1SChristoph Hellwig }
297e8dcf2d1SChristoph Hellwig 
298e8dcf2d1SChristoph Hellwig static const struct file_operations blk_error_injection_fops = {
299e8dcf2d1SChristoph Hellwig 	.owner		= THIS_MODULE,
300e8dcf2d1SChristoph Hellwig 	.write		= blk_error_injection_write,
301e8dcf2d1SChristoph Hellwig 	.read		= seq_read,
302e8dcf2d1SChristoph Hellwig 	.open		= blk_error_injection_open,
303e8dcf2d1SChristoph Hellwig 	.release	= blk_error_injection_release,
304e8dcf2d1SChristoph Hellwig };
305e8dcf2d1SChristoph Hellwig 
306e8dcf2d1SChristoph Hellwig void blk_error_injection_init(struct gendisk *disk)
307e8dcf2d1SChristoph Hellwig {
308e8dcf2d1SChristoph Hellwig 	debugfs_create_file("error_injection", 0600, disk->queue->debugfs_dir,
309e8dcf2d1SChristoph Hellwig 			disk, &blk_error_injection_fops);
310e8dcf2d1SChristoph Hellwig }
311e8dcf2d1SChristoph Hellwig 
312e8dcf2d1SChristoph Hellwig void blk_error_injection_exit(struct gendisk *disk)
313e8dcf2d1SChristoph Hellwig {
314e8dcf2d1SChristoph Hellwig 	error_inject_removeall(disk);
315e8dcf2d1SChristoph Hellwig }
316